读《java并发编程实战》第五章学习记录:该章节主要介绍一些并发编程中一些基本的构建模块。如并发容器和并发工具类(闭锁和栅栏)以及一些需要注意的情况
并发容器
1. ConcurrentHashMap :
对HashMap的散列表进行分段加锁,从而实现较高的并发访问需求,但实现并发需求的同时,像一些需要迭代全集合的方法如果 size()返回的值可能就不是非常准确的,这是它的缺点 .大多数并发情况下应该采用ConcurrentHashMap,但一些对原子性要求较高的操作,如程序需要对整个map进行独占式访问的时候,采用Collections.synchronziedMap 这个集合
2. CopyOnWriteArrayList :
从名字上就可以看出,在写的时候拷贝列表。应用场景:当迭代操作占主导,更新操作较少的情况下使用该容器比较好
同步工具类
1. 闭锁(CountDownLatch): 根据jdk给的示例代码,主要有两种应用场景: a,工作线程需要同时启动,先完成工作的线程需要等待至所有线程完成任务方可执行下一步操作,如下Driver示例:b,将任务分成相同的N份,待所有的部分都完成后,再执行后续操作,如Driver2所示:
class Driver { // ... void main() throws InterruptedException { CountDownLatch startSignal = new CountDownLatch(1); CountDownLatch doneSignal = new CountDownLatch(N); for (int i = 0; i < N; ++i) // create and start threads new Thread(new Worker(startSignal, doneSignal)).start(); doSomethingElse(); // don‘t let run yet startSignal.countDown(); // let all threads proceed doSomethingElse(); doneSignal.await(); // wait for all to finish } } class Worker implements Runnable { private final CountDownLatch startSignal; private final CountDownLatch doneSignal; Worker(CountDownLatch startSignal, CountDownLatch doneSignal) { this.startSignal = startSignal; this.doneSignal = doneSignal; } public void run() { try { startSignal.await(); //初始化后的线程们在闭锁上阻塞,当主线程调用startSignal.countDown()时,所有线程开始运行 doWork(); doneSignal.countDown(); } catch (InterruptedException ex) {} // return; } void doWork() { ... } }
class Driver2 { // ... void main() throws InterruptedException { CountDownLatch doneSignal = new CountDownLatch(N); Executor e = ... for (int i = 0; i < N; ++i) // create and start threads e.execute(new WorkerRunnable(doneSignal, i)); //任务放入线程池 doneSignal.await(); // wait for all to finish } } class WorkerRunnable implements Runnable { private final CountDownLatch doneSignal; private final int i; WorkerRunnable(CountDownLatch doneSignal, int i) { this.doneSignal = doneSignal; this.i = i; } public void run() { try { doWork(i); doneSignal.countDown(); // } catch (InterruptedException ex) {} // return; } void doWork() { ... } }
2. 栅栏(CyclicBarrier):其实栅栏的作用和闭锁的第二种应用场景很相似,都是将一个大任务分解成多个相同的小任务执行,每个小任务对应一个工作线程。它们的区别在于栅栏是可以重复使用的,而闭锁是一次性的。(引用api中的解释:latch is a one-shot phenomenon -- the count cannot be reset. If you need a version that resets the count, consider using a CyclicBarrier ; The barrier is called cyclic because it can be re-used after the waiting threads are released. )所以说,栅栏适用于小任务需要重复执行的情况。下面引用jdk的示例代码:
class Solver { final int N; final float[][] data; final CyclicBarrier barrier; class Worker implements Runnable { int myRow; Worker(int row) { myRow = row; } public void run() { while (!done()) { processRow(myRow); //处理所有行的大任务分解成多个处理一行的小任务,每个相同的小任务对应一个工作线程 try { barrier.await(); //每个线程处理完分配给自己的小任务后,阻塞等待。直到所有的线程都完成任务,才release } catch (InterruptedException ex) { return; } catch (BrokenBarrierException ex) { return; } } } } public Solver(float[][] matrix) { data = matrix; N = matrix.length; barrier = new CyclicBarrier(N, new Runnable() { //当所有的工作线程都release的时候,执行该Runnable对象,进行一些“收尾”工作,如下面的合并行 public void run() { mergeRows(...); } }); for (int i = 0; i < N; ++i) new Thread(new Worker(i)).start(); //为每一行分配一个工作线程 waitUntilDone(); } }
3.信号量(Semaphore):用来限定资源的可访问线程数量,线程访问资源需要先获得许可;
4.FutureTask:代表一个开销较大的异步任务
5. 阻塞队列(BlockQueue):适用于 “生产者和消费者”的应用场景。参照书上给的例子,写了一个demo:生产者读取文件到阻塞队列,消费者从队列中取出文件并打印文件名
/** *生产者线程,负责将文件放入阻塞队列 */ public class Producer implements Runnable{ private File root; private BlockingQueue<File> queue; public Producer(File root, BlockingQueue<File> queue) { super(); this.root = root; this.queue = queue; } @Override public void run() { try { putFilesIntoQueue(root); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * @param root * @throws InterruptedException * 以给定文件为跟将root下的所有文件名都放入阻塞队列中 */ private void putFilesIntoQueue(File root) throws InterruptedException { File[] subFiles = root.listFiles(); for(File file: subFiles){ if(file.isFile()){ queue.put(file); }else{ putFilesIntoQueue(file); } } } }
/** *消费者线程,负责将文件从阻塞队列中取出 */ public class Consumer implements Runnable{ private BlockingQueue<File> queue; public Consumer(BlockingQueue<File> queue) { super(); this.queue = queue; } @Override public void run() { try { getOutFromQueue(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * @param root * @throws InterruptedException * 从阻塞队列中取出文件并打印出文件名 */ private void getOutFromQueue() throws InterruptedException { while(true){ String name=queue.take().getName(); System.out.println(Thread.currentThread().getName()+": "+name); } } }
/** *测试客户端 */ public class Main { public static void main(String[] args) { File[] roots={ new File("C://"),new File("D://"), new File("E://"),new File("F://") }; BlockingQueue<File> queue=new ArrayBlockingQueue<>(20); //定义一组生产者线程,每个线程对应一个盘符 for(int i=0;i<4;i++){ new Thread(new Producer(roots[i],queue)).start(); } //定义一组消费者线程 for(int i=0;i<10;i++){ new Thread(new Consumer(queue)).start(); } } }
其他
1. 关于中断和阻塞:对于调用可能抛出InterruptException的方法,如sleep(),await()方法时应该如下处理:
1.1 重新抛出该异常
1.2 对于不能重新抛出异常的情况:如下所示,采取Thread.currentThread.interrupt()的方法恢复中断
new Runnable() { public void run() { try{ doSomeThing() }catch(InterruptedException e){ Thread.currentThread.interrupt(); }
为啥这样处理? 看到时,再补充。。
原文:http://www.cnblogs.com/liuwaner118/p/4366233.html