Java并发编程
CompletionService
-
ExecutorService的扩展,可以获得线程执行结果
-
可以将生产新的异步任务与使用已完成任务的结果分离开来。生产者submit 需要执行的任务;使用者 take 已完成的任务的结果
-
代码实例
public class MyCompletionService implements Callable<String>{ @Override public String call() throws Exception { return "******callable run ********"; } public static void main(String[] args) throws Exception{ ExecutorService es = Executors.newCachedThreadPool(); CompletionService<String> completion = new ExecutorCompletionService(es); completion.submit(new MyCompletionService()); String result = completion.take().get(); System.out.println(result); es.shutdown(); } }
分支/合并(fork/join)框架
-
按照用户指定的方式对任务进行分解,然后再将分解出的小型任务的执行结果合并成原来任务的执行结果
-
代码实例,计算n到m之间的总和
public class MyTask extends RecursiveTask<Integer> { private static final int threshold = 100; private int startNum; private int endNum; public MyTask(int startNum, int endNum) { this.startNum = startNum; this.endNum = endNum; } @Override protected Integer compute() { int sum = 0; if ((endNum - startNum) < 100) { for (int i = startNum; i <= endNum; i++) { sum += i; } } else { int middle = (startNum + endNum) / 2; MyTask left = new MyTask(startNum, middle); MyTask right = new MyTask(middle + 1, endNum); left.fork(); right.fork(); sum = left.join() + right.join(); } return sum; } public static void main(String[] args) throws Exception { ForkJoinPool pool = new ForkJoinPool(); Future<Integer> result = pool.submit(new MyTask(1, 10000)); System.out.println(result.get()); } }
Semaphore
-
通常用于限制访问资源的线程数目
-
一个计数信号量。从概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前会通过acquire()阻塞每一个线程,直到可以获取该许可,线程每次调用release()均释放一个许可,从而可能会让另外一个线程获得许可。事实上Semaphore不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
-
Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。
-
这里是一个实际的情况,大家排队上厕所,厕所只有两个位置,来了10个人需要排队
public class MySemaphore extends Thread { private Semaphore position; private int id; public MySemaphore(int i, Semaphore s) { this.id = i; this.position = s; } public void run() { try { if (position.availablePermits() > 0) { System.out.println("顾客[" + this.id + "]进入厕所,有空位");//这里只是查询到有空位,但是线程不一定能够得到空位,毕竟还没有真正的获取 } else { System.out.println("顾客[" + this.id + "]进入厕所,没空位,排队");//这里只是查询到没有空位,但是线程也可能得到空位,因为在此期间可能有线程已经释放了空位 } position.acquire(); System.out.println("顾客[" + this.id + "]获得坑位"); Thread.sleep((int) (Math.random() * 1000)); System.out.println("顾客[" + this.id + "]使用完毕"); position.release(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { ExecutorService list = Executors.newCachedThreadPool(); Semaphore position = new Semaphore(2); for (int i = 0; i < 10; i++) { list.submit(new MySemaphore(i + 1, position)); } list.shutdown(); position.acquireUninterruptibly(2); System.out.println("使用完毕,需要清扫了"); position.release(2); } }
ReentrantLock
-
ReentrantLock与synchronized的比较
- 相同:ReentrantLock提供了synchronized类似的功能和内存语义
- 不同:
- ReentrantLock功能性方面更全面,比如时间锁等候,可中断锁等候,锁投票等,因此更有扩展性。在多个条件变量和高度竞争锁的地方,用ReentrantLock更合适,ReentrantLock还提供了Condition,对线程的等待和唤醒等操作更加灵活,一个ReentrantLock可以有多个Condition实例,所以更有扩展性。
- ReentrantLock 的性能比synchronized会好点。
- ReentrantLock提供了可轮询的锁请求,他可以尝试的去取得锁,如果取得成功则继续处理,取得不成功,可以等下次运行的时候处理,所以不容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁
-
实例代码:
Lock lock = new ReentrantLock(); lock.lock(); try { // update object state }finally { lock.unlock(); }
ReentrantReadWriteLock
-
读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁
-
锁机制的特性:
- 重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。
- WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有。反过来ReadLock想要升级为WriteLock则不可能,为什么?参看(a),呵呵.
- ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。
- 不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。
- WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常
-
实例代码
public class ReadWriteLockTest { public static void main(String[] args) { final Queue3 q3 = new Queue3(); for (int i = 0; i < 3; i++) { new Thread() { public void run() { while (true) { q3.get(); } } }.start(); } for (int i = 0; i < 3; i++) { new Thread() { public void run() { while (true) { q3.put(new Random().nextInt(10000)); } } }.start(); } } } class Queue3 { private Object data = null; //共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。 private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public void get() { rwl.readLock().lock(); //上读锁,其他线程只能读不能写 System.out.println(Thread.currentThread().getName() + " be ready to read data!"); try { Thread.sleep((long) (Math.random() * 1000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "have read data :" + data); rwl.readLock().unlock(); //释放读锁,最好放在finnaly里面 } public void put(Object data) { rwl.writeLock().lock(); //上写锁,不允许其他线程读也不允许写 System.out.println(Thread.currentThread().getName() + " be ready to write data!"); try { Thread.sleep((long) (Math.random() * 1000)); } catch (InterruptedException e) { e.printStackTrace(); } this.data = data; System.out.println(Thread.currentThread().getName() + " have write data: " + data); rwl.writeLock().unlock(); //释放写锁 } }
BlockingQueue
-
是一个线程安全的队列,可以解决生产者/消费者的问题,在调用put方法向队列中插入元素时,如果队列已满,它会让插入元素的线程等待队列腾出空间;在调用take方法从队列中取元素时,如果队列为空,取出元素的线程就会阻塞,可以用TransferQueue代替BlockingQueue,因为它可以获得更好的性能
-
代码示例
BlockingQueue<String> buffer = new LinkedBlockingQueue<String>(); buffer.put("hehe"); String str = buffer.take(); //上面代码在多线程环境不会报数组越界异常
CountDownLatch
-
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待
-
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务
-
主要方法
- public CountDownLatch(int count);
- public void countDown();//当前线程调用此方法,则计数减一
- public void await();//调用此方法会一直阻塞当前线程,直到计时器的值为0
-
代码示例
public class CountDownLatchDemo { final static SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(2); //两个工人的协作 Worker worker1 = new Worker("zhang san", 5000, latch); Worker worker2 = new Worker("li si", 8000, latch); worker1.start(); // worker2.start(); // latch.await(); //等待所有工人完成工作 System.out.println("all work done at " + sdf.format(new Date())); } static class Worker extends Thread { String workerName; int workTime; CountDownLatch latch; public Worker(String workerName, int workTime, CountDownLatch latch) { this.workerName = workerName; this.workTime = workTime; this.latch = latch; } public void run() { System.out.println("Worker " + workerName + " do work begin at " + sdf.format(new Date())); doWork(); //工作了 System.out.println("Worker " + workerName + " do work complete at " + sdf.format(new Date())); latch.countDown(); //工人完成工作,计数器减一 } private void doWork() { try { Thread.sleep(workTime); } catch (InterruptedException e) { e.printStackTrace(); } } } }
CyclicBarrier
-
让线程执行到await()方法就暂停下来,当所有线程都执行了await()方法,所有线程立刻一齐 执行下面的代码
-
代码实例(赛跑时,等待所有人都准备好时,才起跑)
public class CyclicBarrierTest { public static void main(String[] args) throws IOException, InterruptedException { //如果将参数改为4,但是下面只加入了3个选手,这永远等待下去 //Waits until all parties have invoked await on this barrier. CyclicBarrier barrier = new CyclicBarrier(3); ExecutorService executor = Executors.newFixedThreadPool(3); executor.submit(new Thread(new Runner(barrier, "1号选手"))); executor.submit(new Thread(new Runner(barrier, "2号选手"))); executor.submit(new Thread(new Runner(barrier, "3号选手"))); executor.shutdown(); } } class Runner implements Runnable { // 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point) private CyclicBarrier barrier; private String name; public Runner(CyclicBarrier barrier, String name) { super(); this.barrier = barrier; this.name = name; } @Override public void run() { try { Thread.sleep(1000 * (new Random()).nextInt(8)); System.out.println(name + " 准备好了..."); // barrier的await方法,在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。 barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println(name + " 起跑!"); } }
-
CountDownLatch和CyclicBarrier的区别
- 闭锁CountDownLatch做减计数,而栅栏CyclicBarrier则是加计数
- CountDownLatch是一次性的,CyclicBarrier可以重用
- CountDownLatch强调一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行CyclicBarrier强调N个线程相互等待,任何一个线程没完成之前,所有的线程都必须等待
- 鉴于上面的描述,CyclicBarrier在一些场景中可以替代CountDownLatch实现类似的功能
ConcurrentHashMap
-
线程安全,效率比HashTable更高
-
原理: 它引入了一个“分段锁”的概念,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放到哪个HashTable中。在ConcurrentHashMap中, 就是把Map分成了N个Segment,put和get的时候,都是现根据key.hashCode()算出放到哪个Segment中ConcurrentHashMap中默认是把segments初始化为长度为16的数组
ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<Integer, Integer>(); map.put(3, 33);