java高并发程序设计总结五:jdk并发包其他同步控制工具类:ReadWriteLock/CountDownLatch/CyclicBarrier/LockSupport
来源:互联网 发布:新sat阅读 知乎 编辑:程序博客网 时间:2024/05/16 19:57
读写锁ReadWriteLock
之前的博客介绍了synchronized和重入锁ReentrantLock都可以实现线程同步,这两种方式确实实现了线程同步,保证了同时只能有一个线程能获得锁资源。不过他们有一个缺点:多个线程对数据进行读取操作也是需要进行等待的。而这实际上是没必要的,因为读操作不会对数据造成污染。ReadWriteLock的出现优化了这一个缺点:多个线程读不会阻塞,而读写和写写会进行阻塞。
ReadWriteLock是一个接口,通常我们可以使用它的一个实现类:ReentrantReadWriteLock,该实现类的声明如下
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { private static final long serialVersionUID = -6992448646407690164L; /** Inner class providing readlock */ private final ReentrantReadWriteLock.ReadLock readerLock; /** Inner class providing writelock */ private final ReentrantReadWriteLock.WriteLock writerLock; ...}
这是其一部分,通过这里可以看到,它是可以序列化的,并且内部包含了两个私有的final变量:readLock/writeLock。这两个变量分别表示着读锁和写锁,我们可以通过内部提供的相应的方法来获取。
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
使用读写锁的示例code
package org.blog.controller;import java.util.Random;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;import java.util.concurrent.locks.ReentrantReadWriteLock;/** * @ClassName: ReadWriteLockDemo * @Description: 读写锁测试类 * @author Chengxi * @Date: 2017-10-23下午1:00:21 * * */public class ReadWriteLockDemo { public static void main(String[] args){ //读线程 final ReadWriteLockDemo demo = new ReadWriteLockDemo(); Runnable read = new Runnable(){ public void run(){ System.out.println(demo.read(readlock)); //System.out.println(demo.read(lock)); } }; //写线程 Runnable write = new Runnable(){ public void run(){ demo.write(lock, new Random().nextInt()); } }; //分别开启10个读线程和写线程 for(int i=0; i<10; i++){ new Thread(read).start(); }// for(int i=0; i<10; i++){// new Thread(write).start();// } } public static ReentrantLock lock = new ReentrantLock(); public static ReentrantReadWriteLock readwritelock = new ReentrantReadWriteLock(); public static Lock readlock = readwritelock.readLock(); public static Lock writelock = readwritelock.writeLock(); private int value = 0; public int read(Lock lock){ lock.lock(); try{ Thread.sleep(1000); return value; } catch(Exception e){ e.printStackTrace(); return -1; } finally{ lock.unlock(); } } public void write(Lock lock, int value){ lock.lock(); try{ Thread.sleep(2000); this.value = value; } catch(Exception e){ e.printStackTrace(); } finally{ lock.unlock(); } }}
在这里可以值测试读操作就行了,因为写操作和重入锁是一样都,都需要进行等待。在读操作那里,我们使用重入锁lock会发现每次都只有一个线程进行读,因此最终完成的时间为10秒;而使用读写所readlock来进行读时,我们会发现最终只需要一秒。这就是他们之间的性能差别(ReadWriteLock适用于读多写少的场景)
倒计时器:CountDownLatch
CountDownLatch是用来指定当前只能同时有几个线程处于执行状态,待这几个线程全部执行完成之后才能继续往下执行,期间会一直处于等待状态。CountDownLatch只有一个构造器,需要传入一个int变量,表示这个计时器的计数个数:
public CountDownLatch(int count){ if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count);}如果count小于0则会抛出异常
它内部主要提供了两个方法:await和countDown,他们的签名如下:
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1);}调用await使当前线程处于等待状态,响应中断public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));}表示等待指定时间,当时间到了计时器个数还是不为0则返回false,否则返回true并继续执行public void countDown() { sync.releaseShared(1);}调用countDown表示当前计数器减1
这两个方法是搭配使用的,计时器的执行步骤为:首先new一个计时器,并指定计时个数;然后在当前线程中创建多线程环境,同时调用await使当前线程处于等待状态,当countDown调用次数等于计时个数时,则当前线程等待完毕,继续执行下去。CountDownLatch内部维护这一个计数器,初始化为Math.min(当前执行线程个数,count),当调用await进行等待时,每次调用countDown计数器都会自减1,当计数器的值为0时,结束等待
CountDownLatch测试code
package org.blog.controller;import java.util.Random;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * @ClassName: CountDownLatchDemo * @Description: 倒计时器:CountDownLatch * @author Chengxi * @Date: 2017-10-23下午1:16:09 * * */public class CountDownLatchDemo { public static CountDownLatch latch = new CountDownLatch(10); public static void main(String[] args) throws InterruptedException { Thread test = new Thread(){ public void run(){ try{ Thread.sleep(new Random().nextInt(10)*1000); System.out.println("check complete"); latch.countDown(); } catch(InterruptedException e){ e.printStackTrace(); } } }; ExecutorService exec = Executors.newFixedThreadPool(12); for(int i=0; i<12; i++){ exec.submit(test); } latch.await(); System.out.println("fire!"); exec.shutdown(); }}
上面的代码运行结果为:先执行线程池中的前面十个线程,然后计时器释放当前线程,main主线程继续执行,然后线程池里的2个线程执行;(这里需要说明的是在计时器释放之后,main主线程和线程池里面的两个剩余线程是多线程执行的,不过因为main中不需要sleep等待,而线程池中的需要等待随机事件,所以大部分都是先执行main,在执行两个线程)。如果将CountDownLatch中的计数器个数设置为13,则程序会一直等待,不会打印fire,因为计时器永远不会为0,一直为1
循环栅栏:CyclicBarrier
CyclicBarrier和CountDownLatch一样,都可以用来实现线程间的计数等待;不过他的功能要比CountDownLatch强大。它的两个构造器如下:
public CyclicBarrier(int parties) { this(parties, null);}用于创建一个CyclicBarrier对象并指定等待个数(这里没有指定runnable,则当等待数达到了parties数量时,不执行任何回调操作)public CyclicBarrier(int parties, Runnable barrierAction) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; this.barrierCommand = barrierAction;}用于创建一个CyclicBarrier对象并指定等待个数,并且当等待数到了parties时启动barrierAction线程,在parties个等待线程被唤醒前执行
循环栅栏可以多次计数,相比CountDownLatch的await/countDown来说,这里仅仅使用await方法,每次调用就会计数一次线程等待数,当数值等于parties时执行创建时的回调线程,然后启动所有等待线程(类似于一种分批次排队,人数够了就开始)。await方法签名如下:
public int await() throws InterruptedException, BrokenBarrierException
该方法响应中断,在等待时被中断会抛出中断异常,而他也可能抛出BrokenBarrierException异常,表示当前的CyclicBarrier已经破损了,可能系统没有办法等待所有线程到齐了。比如我们在await的过程中中断一个线程,而cyclicBarrier必须要等他们所有线程await,所以这时候是没有办法等到所有的了,因此就会抛出一个InterruptedException和九个BrokenBarrierException
循环栅栏使用场景实例:比如在一次任务中,需要先等10个士兵到齐,再一起去执行任务,然后还要等所有士兵一起回来报道任务完成。这里可以使用循环栅栏进行计数10次等待并等待两次,实现代码为:
package org.blog.controller;import java.util.Random;import java.util.concurrent.CyclicBarrier;/** * @ClassName: CyclicBarrierDemo * @Description: 循环栅栏测试类 CyclicBarrier * @author Chengxi * @Date: 2017-10-23下午3:03:26 * * */public class CyclicBarrierDemo { public static class Soldier implements Runnable{ private String soldier; private CyclicBarrier barrier; public Soldier(CyclicBarrier barrier, String soldier){ this.barrier = barrier; this.soldier = soldier; } public void run(){ try{ //等待十个士兵到齐 barrier.await(); doWork(); //等待十个士兵完成任务 barrier.await(); } catch(Exception e){ e.printStackTrace(); System.out.println("soldier run"); } } //全都到齐之后每个人执行任务 public void doWork(){ try{ Thread.sleep(new Random().nextInt(10)*100); } catch(Exception e){ e.printStackTrace(); System.out.println("dowork"); } System.out.println(soldier+" 完成任务"); } } //每次等待完成之后的一次回调 public static class CallRun implements Runnable{ boolean flag; int N; public CallRun(boolean flag, int n){ this.flag = flag; this.N = n; } public void run(){ if(flag){ System.out.println("司令:【士兵"+N+"个任务完成】"); } else{ System.out.println("司令:【士兵"+N+"个集合完毕】"); flag = true; } } } public static void main(String[] args) { final int N = 10; Thread[] allsoldiers = new Thread[N]; boolean flag = false; CyclicBarrier barrier = new CyclicBarrier(N,new CallRun(flag,N)); System.out.println("集合队伍"); for(int i=0; i<N; i++){ System.out.println("士兵"+i+"来报道"); allsoldiers[i] = new Thread(new Soldier(barrier,"士兵"+i)); allsoldiers[i].start(); } }}输出结果:集合队伍士兵0来报道士兵1来报道士兵2来报道士兵3来报道士兵4来报道士兵5来报道士兵6来报道士兵7来报道士兵8来报道士兵9来报道司令:【士兵10个集合完毕】士兵6 完成任务士兵7 完成任务士兵1 完成任务士兵9 完成任务士兵3 完成任务士兵5 完成任务士兵2 完成任务士兵0 完成任务士兵8 完成任务士兵4 完成任务司令:【士兵10个任务完成】
线程阻塞工具类:LockSupport
LockSupport是一个非常方便使用的线程阻塞工具,它可以在线程内的任意位置让线程阻塞,它弥补了suspend执行在resume之后导致线程无法继续执行的情况、同时也不需要先获得对象锁,也不会抛出中断异常;
使用LockSupport阻塞线程不需要获得对象的锁,也不需要new对象,因为其阻塞park和继续执行unpark的方法都是静态成员,他们的方法签名如下:
public static void park(Object blocker) ;使当前线程阻塞,直到调用对应的unpark释放;期间不会影响其他线程的执行public static void unpark(Thread thread);释放指定线程,使其继续运行
前面说:park/unpark弥补了resume/suspend必须按顺序执行的缺陷,这是因为park和unpark的内部实现原理造成的。对于LockSupport来说,它会为每一个线程初始化一个许可,如果当前线程的许可可用,那么调用park就会使用该许可并立即返回,同时设置该线程的许可为不可用状态,而对于LockSupport来说, 如果当前线程的许可不可用,那么线程就会进入阻塞状态。而unpark方法就会将指定线程的许可变为可用状态,所以最终线程是否阻塞是看该线程的许可是否可用,所以它们之间的先后顺序是没有影响的(不过这里要注意的是,LockSupport的许可和信号量不同,许可不能累加,每个线程最多只能拥有一个可用许可)
测试代码:
package org.blog.controller;import java.util.concurrent.locks.LockSupport;/** * @ClassName: LockSupportDemo * @Description: 线程阻塞工具类测试: LockSupport * @author Chengxi * @Date: 2017-10-23下午5:24:41 * * */public class LockSupportDemo { public static lsthread ls1 = new lsthread("ls1"); public static lsthread ls2 = new lsthread("ls2"); public static class lsthread extends Thread{ public lsthread(String name){ super(name); } public void run(){ synchronized(this){ System.out.println("now is->"+getName()); LockSupport.park(); } } } public static void main(String[] args) throws InterruptedException { ls1.start(); Thread.sleep(1000); ls2.start(); LockSupport.unpark(ls1); LockSupport.unpark(ls2); ls1.join(); ls2.join(); }}输出结果:now is->ls1now is->ls2
从main函数中来看,park和unpark两个方法的执行顺序是不确定的,不过最终的输出结果总会是一样的
不过这里需要注意的是park方法不会抛出InterruptedException,他只是会默默的返回,我们只能够通过Thread.interrupted()等方法来获得中断标记
参考文献
java高并发程序设计第三章
浅谈Java中CyclicBarrier的用法
Lock、synchronized和ReadWriteLock的区别和联系
阅读全文
0 0
- java高并发程序设计总结五:jdk并发包其他同步控制工具类:ReadWriteLock/CountDownLatch/CyclicBarrier/LockSupport
- JDK同步控制工具,JAVA高并发程序设计
- Java线程(CountDownLatch、CyclicBarrier、Semaphore)并发控制工具类
- Java并发工具类CountDownLatch和CyclicBarrier
- Java并发工具类CountDownLatch和CyclicBarrier
- 《Java高并发程序设计》总结--3. JDK并发包
- [Java并发包学习五]CountDownLatch和CyclicBarrier介绍
- [Java并发包学习五]CountDownLatch和CyclicBarrier介绍
- [Java并发包学习五]CountDownLatch和CyclicBarrier介绍
- [Java并发包学习五]CountDownLatch和CyclicBarrier介绍
- [Java并发包学习五]CountDownLatch和CyclicBarrier介绍
- [Java并发包学习五]CountDownLatch和CyclicBarrier介绍
- JDK并发包---(11)线程阻塞工具类:LockSupport
- 实战Java高并发程序设计之LockSupport
- Java并发包:CountDownLatch和CyclicBarrier
- Java并发工具类LockSupport
- 实战Java高并发程序设计之ReadWriteLock
- JAVA多线程系列--并发工具类(CountDownLatch, CyclicBarrier, Semaphore,Exchanger)
- 利用shell脚本从SFTP服务下载上传文件
- help & manual 写产品手册
- CentOS7上解决tomcat不能被外部浏览访问
- JSP JSTL界面是否包含的判断 fn:contains()函数
- 高精度数取余(C\C++)
- java高并发程序设计总结五:jdk并发包其他同步控制工具类:ReadWriteLock/CountDownLatch/CyclicBarrier/LockSupport
- 使用MediaRecorder录制音频
- java中多态的理解--摘自StackOverflow
- vim sublime monokai 配置
- Laravel-Permission 使用心得
- Systemd 入门教程:命令篇
- 日程表
- linux下编译多线程程序, undefined reference to `pthread_create',undefined reference to `pthread_join'
- iOS学习笔记