java 并发编程-构建块
关键字: java concurrent
java5引入了很多新的并发容器和工具,极大的简化了并发程序的编写。本文先介绍Collections.synchronizedXXX工厂方 法创建的同步容器的不足,再介绍 ConcurrentHashMap,CopyOnWriterArrayList,BlockingQueue,CountDownLatch,Semaphore,CyclicBarrier 和显示锁类。
一、引言
所有的并发问题都来源于如何协调访问并发状态,可变状态越少,并发控制也就越容易容易。没有可变状态的对象永远是现成安全。对有可变状态的对象的并 发访问必须进行加锁,对每个方法都加锁并不能保证并发访问一定处于一致的状态。在构建并发程序的时候,可以把并发的控制代理到已有的并发类,比如类库提供 的并发容器或工具。
二、同步容器的不足
对Collections.synchronizedXXX工厂方法创建的同步容器,每个方法都是使用该容器的内部锁进行控制的,这会带来一性能问 题,因为多个安全的读操作也要等待排它锁。即使每个方法的调用都是线程安全的,但同时调用多个操作室,不一定是线程安全的,比如缺少即添加,相等即修改等 炒作,是两步原子操作构成的,和在一起并不是原子的,必须在调用代码里使用容器的锁进行控制。另外在迭代集合的时候,还会由于并发修改而抛出 ConcurrentModefiedExceptioin,这往往是调用程序不希望的结果。
三、ConcurrentHashMap,CopyOnWriterArrayList
ConcurrentHashMap使用的内部细粒度的分离锁,这个锁机制允许任意数量的读线程并发访问,提高了吞吐率。在迭代时不会抛出 ConcurrentModefiedExceptioin,如果在迭代期间有修改发生,返回的是迭代开始时的状态。另外对缺少即添加,相等即修改等二元 操作也有相应的方法支持。ConcurrentHashMap实现了ConcurrentMap提供的几个特殊原子操作:
public V putIfAbsent(K key, V value)
如果指定键已经不再与某个值相关联,则将它与给定值关联。
public boolean remove(Object key, Object value)
只有目前将键的条目映射到给定值时,才移除该键的条目。
public boolean replace(K key, V oldValue, V newValue)
只有目前将键的条目映射到给定值时,才替换该键的条目。
public V replace(K key, V value)
只有目前将键的条目映射到某一值时,才替换该键的条目。
CopyOnWriterArrayList 是ArrayList 的一个并发替代品,通常情况下,它提供比较好的并发性,允许多个现在并发的对其进行迭代。每次需要修改时,便会创建并重新发布一个新的容器拷贝,以此来实现可变性。因为底层使用数组实现,如果数组元素较多是,复制多付出的代价较大。
三、BlockingQueue
阻塞队列提供了可以阻塞的put和take方法,以及与之等价的可以指定超时的offer和poll。如果Queue是空的,那么take方法会一 直阻塞,直到有元素可用。如果Queue是有线长度的,队列满的时候put方法也会阻塞。BlockingQueue可以很好的支持生产者和消费者模式, 生产者往队列里put,消费者从队列里get,两者能够得好很好的同步。BlockingQueue的实现类LinkedBlockingQueue和 ArrayBlockingQueue是FIFO队列,PriorityBlockingQueue是一个按优先级排序的队列。使用 BlockingQueue构建的一个生产者与消费例子:
消费者:
- public class Consumer implements Runnable {
- private BlockingQueue<Food> queue;
- private ExecutorService exec;
- public Consumer(BlockingQueue<Food> queue, ExecutorService exec) {
- this .queue = queue;
- this .exec = exec;
- }
- @Override
- public void run() {
- while (!exec.isShutdown()) {
- try {
- Thread.sleep(2000 );
- Food food = queue.take();
- System.out.println("Consumer " + food);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (RejectedExecutionException e) {
- }
- }
- }
- }
public class Consumer implements Runnable {
private BlockingQueue<Food> queue;
private ExecutorService exec;
public Consumer(BlockingQueue<Food> queue, ExecutorService exec) {
this.queue = queue;
this.exec = exec;
}
@Override
public void run() {
while (!exec.isShutdown()) {
try {
Thread.sleep(2000);
Food food = queue.take();
System.out.println("Consumer " + food);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (RejectedExecutionException e) {
}
}
}
}
生产者:
- public class Producer implements Runnable {
- private BlockingQueue<Food> queue;
- private ExecutorService exec;
- public Producer(BlockingQueue<Food> queue, ExecutorService exec) {
- this .queue = queue;
- this .exec = exec;
- }
- @Override
- public void run() {
- while (!exec.isShutdown()) {
- Food food = new Food();
- try {
- Thread.sleep(4000 );
- queue.put(food);
- System.out.println("Produce " + food);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (RejectedExecutionException e) {
- }
- }
- }
- }
public class Producer implements Runnable {
private BlockingQueue<Food> queue;
private ExecutorService exec;
public Producer(BlockingQueue<Food> queue, ExecutorService exec) {
this.queue = queue;
this.exec = exec;
}
@Override
public void run() {
while (!exec.isShutdown()) {
Food food = new Food();
try {
Thread.sleep(4000);
queue.put(food);
System.out.println("Produce " + food);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (RejectedExecutionException e) {
}
}
}
}
Main:
- BlockingQueue<Food> queue = new ArrayBlockingQueue<Food>( 5 );
- ExecutorService exec = Executors.newFixedThreadPool(3 );
- Producer p1 = new Producer(queue, exec);
- Producer p2 = new Producer(queue, exec);
- Consumer c1 = new Consumer(queue, exec);
- exec.execute(p1);
- exec.execute(p2);
- exec.execute(c1);
- try {
- Thread.sleep(10000 );
- } catch (InterruptedException ignored) {
- }
- exec.shutdown();
BlockingQueue<Food> queue = new ArrayBlockingQueue<Food>(5);
ExecutorService exec = Executors.newFixedThreadPool(3);
Producer p1 = new Producer(queue, exec);
Producer p2 = new Producer(queue, exec);
Consumer c1 = new Consumer(queue, exec);
exec.execute(p1);
exec.execute(p2);
exec.execute(c1);
try {
Thread.sleep(10000);
} catch (InterruptedException ignored) {
}
exec.shutdown();
四、CountDownLatch
闭锁(Latch),它可以延迟线程的进度知道线程到达终止状态。一个闭锁工作方式就像一道门,直到闭锁到达终点状态之前,门一直关闭着。终点状态到了之后,所有阻塞的线程都可以通过。CountDownLatch 使用一个计数器作为终点状态,知道计数器的值到达0时,闭锁才会打开。调用await 方法,线程会阻塞知道计数器为0,countDown 方法使计数器减一。
闭锁有两种常见的用法,开始闭锁,结束闭锁。开始闭锁用于等待一个条件到达后所有线程一起执行,结束闭锁可以用来等待所有条件或所有线程结束后再进行后续处理。例子:
- final CountDownLatch startLatch = new CountDownLatch( 1 );
- final CountDownLatch endLatch = new CountDownLatch( 3 );
- Runnable prepare = new Runnable() {
- @Override
- public void run() {
- try {
- startLatch.await();//等待开始闭锁,线程同时开始执行
- System.out.println("收拾东西,准备出门" );
- Random rnd = new Random();
- Thread.sleep(rnd.nextInt(1000 ));
- } catch (InterruptedException ignored) {
- }
- endLatch.countDown();
- }
- };
- Thread mum = new Thread(prepare);
- Thread dad = new Thread(prepare);
- Thread me = new Thread(prepare);
- mum.start();
- dad.start();
- me.start();
- startLatch.countDown();
- try {
- endLatch.await();
- } catch (InterruptedException ignored) {
- }
- System.out.println("逛街" );
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch endLatch = new CountDownLatch(3);
Runnable prepare = new Runnable() {
@Override
public void run() {
try {
startLatch.await();//等待开始闭锁,线程同时开始执行
System.out.println("收拾东西,准备出门");
Random rnd = new Random();
Thread.sleep(rnd.nextInt(1000));
} catch (InterruptedException ignored) {
}
endLatch.countDown();
}
};
Thread mum = new Thread(prepare);
Thread dad = new Thread(prepare);
Thread me = new Thread(prepare);
mum.start();
dad.start();
me.start();
startLatch.countDown();
try {
endLatch.await();
} catch (InterruptedException ignored) {
}
System.out.println("逛街");
五、Semaphore,信号量
使用信号量进行同步和互斥的控制是最经典的并发模型,java 中也提高支持。一个Semaphore管理一个有效的许可 集,许可基的数量通过构造函数传入,通过acquire方法申请一个许可,许可数为0则阻塞线程,否则许可数减一,使用release方法释放一个许个, 许可数加一。一个技术量为1的Semaphore为二元信号量,相当于一个互斥锁,表示不可重入的锁。一个使用信号量控制并发容器上届的例子:
- public class BoundedHashSet<T> {
- private final Set<T> set;
- private final Semaphore sem;
- public BoundedHashSet( int bound) {
- set = Collections.synchronizedSet(new HashSet<T>());
- sem = new Semaphore(bound);
- }
- public boolean add(T o) throws InterruptedException {
- 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;
- }
- }
public class BoundedHashSet<T> {
private final Set<T> set;
private final Semaphore sem;
public BoundedHashSet(int bound) {
set = Collections.synchronizedSet(new HashSet<T>());
sem = new Semaphore(bound);
}
public boolean add(T o) throws InterruptedException {
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;
}
}
六、CyclicBarrier
关卡(Barrier)类似于闭锁,他们都能阻塞一组线程,知道某些事件发生,不同之处在于所有CyclicBarrier等待的是现线程,只有一 定数目的线程到达这个点时,才允许同时通过。它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。
- public class Main {
- public static CyclicBarrier getCyclicBarrier( int count) {
- if (count <= 0 )
- return null ;
- final CyclicBarrier cyclicBarrier = new CyclicBarrier(count,
- new Runnable() {
- public void run() {
- try {
- Thread.sleep(1000 );
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("conditon is arrive and CycleBarrier is running" );
- }
- });
- return cyclicBarrier;
- }
- public static Thread getThread(String nameOfThread,
- final CyclicBarrier cyclicBarrier) {
- Thread thread = new Thread(nameOfThread) {
- public void run() {
- System.out.println(this .getName() +
- "is begin; and count is " + (++count));
- try {
- cyclicBarrier.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (BrokenBarrierException e) {
- e.printStackTrace();
- }
- System.out.println(this .getName() + "finished" );
- }
- };
- return thread;
- }
- static int count = 0 ;
- public static void main(String[] args) {
- /** define a cyclicBarrier and number of barrier is 2. */
- CyclicBarrier cyclicBarrier = getCyclicBarrier(2 );
- Thread threadOne = getThread("threadOne" , cyclicBarrier);
- threadOne.start();
- Thread threadTwo = getThread("threadTwo" , cyclicBarrier);
- threadTwo.start();
- }
- }
public class Main {
public static CyclicBarrier getCyclicBarrier(int count) {
if (count <= 0)
return null;
final CyclicBarrier cyclicBarrier = new CyclicBarrier(count,
new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("conditon is arrive and CycleBarrier is running");
}
});
return cyclicBarrier;
}
public static Thread getThread(String nameOfThread,
final CyclicBarrier cyclicBarrier) {
Thread thread = new Thread(nameOfThread) {
public void run() {
System.out.println(this.getName() +
"is begin; and count is "+ (++count));
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(this.getName() + "finished");
}
};
return thread;
}
static int count = 0;
public static void main(String[] args) {
/** define a cyclicBarrier and number of barrier is 2. */
CyclicBarrier cyclicBarrier = getCyclicBarrier(2);
Thread threadOne = getThread("threadOne", cyclicBarrier);
threadOne.start();
Thread threadTwo = getThread("threadTwo", cyclicBarrier);
threadTwo.start();
}
}
该例子中CyclicBarrier等待两个线程到达后输出conditon is arrive and CycleBarrier is running,两个线程都从await中返回。
七、显式锁
在java 5之前,用于调节共享对象访问的机制只有synchronized和volatile。java 5提供了新的选择:ReentrantLock。ReentrantLock能够提供更多的高级特性,比如轮询和可定时的加锁,可中断的加锁。以及一个支 持读锁和写锁的ReentrantReadWriteLock。使用ReentrantLock必须手动使用lock或其他操作加锁,在finally块 中unlock。
ReentrantLock:一个可重入的互斥锁Lock,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。 使用ReentrantLock构建的同步Map:
- public class LockedMap<K, V> {
- private Map<K, V> map;
- private Lock lock = new ReentrantLock();
- public LockedMap(Map<K, V> map) {
- this .map = map;
- }
- public V get(K key) {
- lock.lock();
- try {
- return map.get(key);
- } finally {
- lock.unlock();
- }
- }
- public void put(K key, V value) {
- lock.lock();
- try {
- map.put(key, value);
- } finally {
- lock.unlock();
- }
- }
- }
public class LockedMap<K, V> {
private Map<K, V> map;
private Lock lock = new ReentrantLock();
public LockedMap(Map<K, V> map) {
this.map = map;
}
public V get(K key) {
lock.lock();
try {
return map.get(key);
} finally {
lock.unlock();
}
}
public void put(K key, V value) {
lock.lock();
try {
map.put(key, value);
} finally {
lock.unlock();
}
}
}
- public class ReentrantLockTest {
- private List<Integer> numbers = new ArrayList <Integer>();
- private Lock numbersLock = new ReentrantLock();
- public void addNumbers( int num) {
- try {
- numbersLock.lock();
- numbers.add(num);
- } finally {
- numbersLock.unlock();
- }
- }
- public void outputNumbers() {
- try {
- if (numbersLock.tryLock( 1 , TimeUnit.SECONDS)) {
- for ( int num : numbers) {
- System.out.println(num);
- }
- }
- } catch (InterruptedException ex) {
- ex.printStackTrace();
- } finally {
- numbersLock.unlock();
- }
- }
- public static void main(String[] args) {
- final ReentrantLockTest test = new ReentrantLockTest();
- Executor pool = Executors.newFixedThreadPool(3 );
- pool.execute(new Runnable() {
- public void run() {
- Random rnd = new Random();
- while ( true ) {
- int number = rnd.nextInt();
- test.addNumbers(number);
- try {
- Thread.sleep(100 );
- } catch (InterruptedException ignored) {
- }
- }
- }
- });
- pool.execute(new Runnable() {
- public void run() {
- while ( true ) {
- test.outputNumbers();
- try {
- Thread.sleep(1000 );
- } catch (InterruptedException ignored) {
- }
- }
- }
- });
- }
- }
public class ReentrantLockTest {
private List<Integer> numbers = new ArrayList
<Integer>();
private Lock numbersLock = new ReentrantLock();
public void addNumbers(int num) {
try {
numbersLock.lock();
numbers.add(num);
} finally {
numbersLock.unlock();
}
}
public void outputNumbers() {
try {
if (numbersLock.tryLock(1, TimeUnit.SECONDS)) {
for (int num : numbers) {
System.out.println(num);
}
}
} catch (InterruptedException ex) {
ex.printStackTrace();
} finally {
numbersLock.unlock();
}
}
public static void main(String[] args) {
final ReentrantLockTest test = new ReentrantLockTest();
Executor pool = Executors.newFixedThreadPool(3);
pool.execute(new Runnable() {
public void run() {
Random rnd = new Random();
while (true) {
int number = rnd.nextInt();
test.addNumbers(number);
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {
}
}
}
});
pool.execute(new Runnable() {
public void run() {
while (true) {
test.outputNumbers();
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
}
}
});
}
}
ReentrantReadWriteLock提供了对读锁和写锁的支持,同一时刻,可允许多个读锁,但只允许有一个写锁,读锁的获取和写锁的获取 是互斥的。从ReentrantReadWriteLock对象的readLock方法可以获得相应的读锁,writeLock方法可以获得相应的写锁。 使用ReentrantReadWriteLock构建的Map,允许多个get操作并发执行:
- public class ReadWriteMap<K,V> {
- private Map<K,V> map;
- private ReadWriteLock lock = new ReentrantReadWriteLock();
- private Lock readLock = lock.readLock();
- private Lock writeLock = lock.writeLock();
- public ReadWriteMap(Map<K,V> map){
- this .map = map;
- }
- public V get(K key){
- readLock.lock();
- try {
- return map.get(key);
- }
- finally {
- readLock.unlock();
- }
- }
- public void put(K key,V value){
- writeLock.lock();
- try {
- map.put(key, value);
- }
- finally {
- writeLock.unlock();
- }
- }
- }
public class ReadWriteMap<K,V> {
private Map<K,V> map;
private ReadWriteLock lock = new ReentrantReadWriteLock();
private Lock readLock = lock.readLock();
private Lock writeLock = lock.writeLock();
public ReadWriteMap(Map<K,V> map){
this.map = map;
}
public V get(K key){
readLock.lock();
try{
return map.get(key);
}
finally{
readLock.unlock();
}
}
public void put(K key,V value){
writeLock.lock();
try{
map.put(key, value);
}
finally{
writeLock.unlock();
}
}
}
所有代码见附件。本文参考《Java 并发编程实践 》。