CountDownLatch,CyclicBarrier,Semaphore,ThreadLocal用法总结
Java CountDownLatch,CyclicBarrier,Semaphore用法
一、 CountDownLatch
CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
CountDownLatch只提供一个构造器,构造参数count,从jdk注释可得出结论:在线程通过await()方法之前,必须要调用countDown()方法,count次数。
await()方法就是让线程等待count为0的方法。
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
参照jdk注释,CountDownLatch用法:
package com.nt.test.current_thread; import java.util.concurrent.CountDownLatch; /** * Create by TaoTaoNing * 2019/3/31 **/ public class CountDownLatchTest { public static void main(String[] args) { final CountDownLatch countDownLatch = new CountDownLatch(4); for (int i = 0;i<4;i++){ new Thread(new CountDownThread(countDownLatch)).start(); } new Thread(new Runnable() { @Override public void run() { System.out.println("排队线程开始执行----"); try { // 排队线程等待 count为0之后通过 await方法 countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("排队线程执行完毕----"); } }).start(); System.out.println("主线程-----------"); } } class CountDownThread implements Runnable{ private CountDownLatch countDownLatch; public CountDownThread(CountDownLatch countDownLatch){ this.countDownLatch = countDownLatch; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "--" + "开始执行----" + countDownLatch.getCount()); try { Thread.sleep(3000); // count 数减少 countDownLatch.countDown(); System.out.println(Thread.currentThread().getName() + "--" + "执行完毕------"+countDownLatch.getCount()); } catch (InterruptedException e) { e.printStackTrace(); } } }
输出结果:(可能打印语句执行时间问题,可能顺序有所不同)
Thread-0--开始执行----4 主线程----------- Thread-1--开始执行----4 Thread-2--开始执行----4 Thread-3--开始执行----4 排队线程开始执行---- Thread-2--执行完毕------2 Thread-1--执行完毕------0 Thread-0--执行完毕------1 Thread-3--执行完毕------2 排队线程执行完毕----
还有一个await(long timeout, TimeUnit unit)方法,与await()方法类似,只不过是指定等待的时间后若count不为零,则继续排队线程。
二 CyclicBarrier
字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。
CyclicBarrier 有两个构造方法:
public CyclicBarrier(int parties) { this(parties, null); } public CyclicBarrier(int parties, Runnable barrierAction) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; this.barrierCommand = barrierAction; }
一个参数的就是指定多少个线程会作为一组同时执行,而barrierAction参数就是指定执行线程
并发类回环栅栏方法测试
await()方法和CountDownLatch类似,线程等待方法。带参数的await方法也类似。
package com.nt.test.current_thread; import java.util.concurrent.CyclicBarrier; /** * Create by TaoTaoNing * 2019/3/31 * 假设有若干个线程要执行任务一,并且只有所有线程执行完毕后再 * 进行其他操作 **/ public class CyclicBarrierTest { public static void main(String[] args) throws Exception{ CyclicBarrier cyclicBarrier = new CyclicBarrier(4); for (int i = 0; i < 10;i++){ new Thread(new Operator(cyclicBarrier)).start(); Thread.sleep(3000); } } } class Operator implements Runnable { private CyclicBarrier cyclicBarrier; public Operator(CyclicBarrier cyclicBarrier) { this.cyclicBarrier = cyclicBarrier; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "-----开始执行"); try { // 模拟执行时间 Thread.sleep(3000); System.out.println(Thread.currentThread().getName() + "-----执行完毕任务 1--" + System.currentTimeMillis()); cyclicBarrier.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "-----开始执行其它任务 "); } }
输出:如果执行的线程数不是 CyclicBarrier(int parties) 中parties的倍数,则会等待直到等待线程数达到parties才执行。
Thread-0-----开始执行 Thread-0-----执行完毕任务 1--1554038221368 Thread-1-----开始执行 Thread-1-----执行完毕任务 1--1554038224378 Thread-2-----开始执行 Thread-2-----执行完毕任务 1--1554038227384 Thread-3-----开始执行 Thread-3-----执行完毕任务 1--1554038230387 Thread-3-----开始执行其它任务 Thread-0-----开始执行其它任务 Thread-2-----开始执行其它任务 Thread-1-----开始执行其它任务 Thread-4-----开始执行 Thread-4-----执行完毕任务 1--1554038233389 Thread-5-----开始执行 Thread-5-----执行完毕任务 1--1554038236395 Thread-6-----开始执行 Thread-6-----执行完毕任务 1--1554038239404 Thread-7-----开始执行 Thread-7-----执行完毕任务 1--1554038242410 Thread-7-----开始执行其它任务 Thread-6-----开始执行其它任务 Thread-4-----开始执行其它任务 Thread-5-----开始执行其它任务 Thread-8-----开始执行 Thread-8-----执行完毕任务 1--1554038245426 Thread-9-----开始执行 Thread-9-----执行完毕任务 1--1554038248441
注意的是CyclicBarrier可以重用:
public static void main(String[] args) throws Exception{ CyclicBarrier cyclicBarrier = new CyclicBarrier(4); for (int i = 0; i < 10;i++){ new Thread(new Operator(cyclicBarrier)).start(); Thread.sleep(3000); } System.out.println("barrier 重用"); for (int i = 0; i < 10;i++){ new Thread(new Operator(cyclicBarrier)).start(); } } }
Semaphore
Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
Semaphore 构造方法
public Semaphore(int permits) { //参数permits表示许可数目,即同时可以允许多少线程进行访问 sync = new NonfairSync(permits); } public Semaphore(int permits, boolean fair) { //这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可 sync = (fair)? new FairSync(permits) : new NonfairSync(permits); }
方法说明:
cquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。
release()用来释放许可。注意,在释放许可之前,必须先获获得许可。
availablePermits()方法得到可用的许可数目。
代码使用:
package com.nt.test.current_thread; import java.util.concurrent.Semaphore; /** * Create by TaoTaoNing * 2019/3/31 * 控制同时访问数据的线程个数 * 假设有四台机器,每台机器同一时间只能一个工人使用 * 有九个工人需要使用这四台机器 **/ public class SemaphoreTest { public static void main(String[] args) { Semaphore semaphore = new Semaphore(4); for (int i = 0; i < 10; i++) { new Thread(new Worker(semaphore,i)).start(); } } } class Worker implements Runnable { private Semaphore semaphore; private int number; public Worker(Semaphore semaphore, int number) { this.number = number; this.semaphore = semaphore; } @Override public void run() { try { semaphore.acquire(); System.out.println("工人 " + number + " ---获取一个机器-"); // 模拟工人工作时间 Thread.sleep(3000); System.out.println("工人 " + number + " ---释放一个机器-"); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } }
输出:
工人 0 ---获取一个机器- 工人 1 ---获取一个机器- 工人 2 ---获取一个机器- 工人 3 ---获取一个机器- 工人 2 ---释放一个机器- 工人 0 ---释放一个机器- 工人 3 ---释放一个机器- 工人 1 ---释放一个机器- 工人 6 ---获取一个机器- 工人 5 ---获取一个机器- 工人 4 ---获取一个机器- 工人 7 ---获取一个机器- 工人 5 ---释放一个机器- 工人 7 ---释放一个机器- 工人 4 ---释放一个机器- 工人 6 ---释放一个机器- 工人 9 ---获取一个机器- 工人 8 ---获取一个机器- 工人 8 ---释放一个机器- 工人 9 ---释放一个机器-
ThreadLocal
线程私有存储对象,每个线程都有单独的存储空间。
这样说起来有些抽象,曾经看过一个大V举过一个例子来描述ThreadLocal的机制,大概是:
比如我们出门时可能会需要先坐公交再坐地铁,这里的坐公交和坐地铁就好比同一个线程内的两个函数,而我自己本身就是一个线程,我要完成这两个函数都需要一个东西–公交卡(坐地铁和公交通用的),那么我为了不向这两个函数都传递公交卡这个变量(相当于不是一直带着公交卡上路),我可以这么做:将公交卡事先交给一个机构,当我需要刷卡时再向这个机构要公交卡(当然每次拿出的都是同一个我自己的公交卡)。这样就能达到只要是我(同一个线程)需要公交卡,何时何地都能向这个机构要的目的。(可能设置公交卡为全局变量也可以何时何地取出公交卡。但是如果有很多个人(线程),大家不能都使用同一张公交卡吧(假设公交卡时实名认证的))。这就是ThreadLocal的设计,提供线程内部的局部变量,在本地线程内随时随地可取,隔离其他线程。
ThreadLocal至少从两方面完成数据的访问的隔离:
纵向隔离–:线程与线程之间的数据访问隔离,这一点有线程的数据结构保证。因为每个线程在进行对象访问时,访问的都是各自线程自己的ThreadLocalMap。(ThreadLocal实现原理)。
横向隔离–:同一线程中,不同的ThreadLocal实例操作的对象之间的相互隔离。这一点由ThreadLocalMap在存储时,采用当前的ThreadLocal的实例作为Key来保证。
其它关于ThreadLocal的描述(eg:与synchronized区别)参见:
@yehx–关于ThreadLocal的认识
当ThreadLocal内部存储的是对象时,由于ThreadLocal的底层实现机制是Map,存储的是内存地址,所以线程隔离可能会失效。
- Synchronizer 闭锁(CountDownLatch,FutureTask ) 信号量(Semaphore) 关卡(CyclicBarrier) 知识点总结(java并发编程实践读书笔记三)
- CountDownLatch、CyclicBarrier和Semaphore用法
- Java多线程:CountDownLatch、CyclicBarrier 和 Semaphore
- Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
- Java并发之CountDownLatch、CyclicBarrier和Semaphore
- CountDownLatch、CyclicBarrier和Semaphore 使用示例及原理
- 并发编程(5)——AQS之CountDownLatch、Semaphore、CyclicBarrier
- Java_并发线程_Semaphore、CountDownLatch、CyclicBarrier、Exchanger
- Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
- java多线程之闭锁(CountDownLatch)、同步屏幕(CyclicBarrier)、信号量(Semaphore)
- Java并发之CountDownLatch、CyclicBarrier和Semaphore
- 使用Java辅助类(CountDownLatch、CyclicBarrier、Semaphore)并发编程
- Java并发编程:CountDownLatch、CyclicBarrier和 Semaphore
- 菜鸟之路——Java并发(七)CountDownLatch、CyclicBarrier和Semaphore
- java并发编程——八 理解分析并发组件-CountDownLatch\CyclicBarrier\Exchanger\Semaphore
- Java并发之CountDownLatch、CyclicBarrier和Semaphore
- Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
- Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
- 同步工具类:CountDownLatch、CyclicBarrier和Semaphore
- 一、基础篇--1.3进程和线程-CountDownLatch、CyclicBarrier 和 Semaphore