同步工具类
Vector和Hashtable同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁来保护复合操作。容器上常见的复合操作包括:迭代、根据指定顺序找到当前元素的下一个元素、若没有就添加。
如下代码
// 在某些情况下:在获得lastIndex和get之间时,list的元素减少了,就会抛出异常
public static Object getLast(Vector list){
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
通过,获得容器类的锁,可以使getLast成为原子操作。代码如下:
public static Object getLast(Vector list){
synchronized(list){
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
}
如果不希望在迭代期间对容器加锁,那么一种替代方法是“克隆”,并在副本上进行迭代。在克隆容器时存在显著的性能开销,这种方式的好坏取决于多个因素。
并发容器
Java5.0提供了多种并发容器来改进同步容器的性能。它是针对多个线程并发访问设计的。
- 在新的ConcurrentMap接口中增加了对一些常见复合操作的支持,如“若没有则添加”、替换以及条件删除等,不需要在迭代过程中对容器加锁。ConcurrentHashMap并不是将每个方法都在同一个锁上同步并使得每次只能有一个线程访问容器,而是使用一种粒度更细的加锁机制来实现更大程度的共享,这种机制称为分段锁。
- CopyOnWriteArrayList用于代替同步的List,在某些情况下它提供了更好的并发性能,并且在迭代期间不需要对容器进行加锁或复制。原则:仅当迭代操作远远多于修改操作时,才应该使用“写入时复制”容器
- 在类库中包含中了阻塞队列BlockingQueue的多种实现。其中,LinkedBlockingQueue和ArrayBlockingQueue是先进先出队列,二者分别与LinkedList和ArrayList类似,但比同步List拥用更好的并发性能。
它是一种同步工具类。CountDownLatch是一种灵活的闭锁实现,它可以使一个或多个线程等待一组事件发生。
如下程序所示:
// 在计时测试时使用CountDownLatch来启动和停止线程
import java.util.concurrent.*;
public class TestHarness {
public long timeTasks(int nThreads, final Runnable task)
throws InterruptedException {
// 初始化计数器
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(nThreads);
for (int i = 0; i < nThreads; i++) {
Thread t = new Thread() {
public void run() {
try {
// 等待计数达到零
startGate.await();
try {
task.run();
} finally {
endGate.countDown();
}
} catch (InterruptedException ignored) {
}
}
};
t.start();
}
long start = System.nanoTime();
// 递减计数器
startGate.countDown();
// 等待计数达到零
endGate.await();
long end = System.nanoTime();
return end - start;
}
}
FutureTask
FutureTask也可以用作闭锁,相当于一种生成结果的Runnable。结果使用Future.get获取,它的行为取决于任务的状态。如果任务已经完成,那么get会立即返回结果,否则get将阻塞直到任务进入完成状态,然后返回结果或抛出异常。
信息量
计数信息量用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。计数信号量还可以实现某种资源池,或者对容器实施边界。
如下程序所示:
import java.util.*;
import java.util.concurrent.*;
public class BoundedHashSet <T> {
private final Set<T> set;
// 计数信号量
private final Semaphore sem;
public BoundedHashSet(int bound) {
this.set = Collections.synchronizedSet(new HashSet<T>());
// 指定许可的数量
sem = new Semaphore(bound);
}
public boolean add(T o) throws InterruptedException {
// 获得许可。如果没有许可,那么acquire将阻塞直到有许可
sem.acquire();
boolean wasAdded = false;
try {
wasAdded = set.add(o);
return wasAdded;
} finally {
if (!wasAdded)
// 释放许可
sem.release();
}
}
public boolean remove(Object o) {
boolean wasRemoved = set.remove(o);
if (wasRemoved)
// 释放许可
sem.release();
return wasRemoved;
}
}
栅栏
栅栏类似于闭锁,它能阻塞一组线程直到某个事件发生。CyclieBarier可以使一定数量的参与方反复地在栅栏位置汇聚,它在并行迭代算法中非常有用:这种算法通常将一个问题拆分成一系列相互独立的子问题。线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。