您的位置:首页 > 其它

CountDownLatch,CyclicBarrier,Semaphore,ThreadLocal用法总结

2019-03-31 22:05 330 查看

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,存储的是内存地址,所以线程隔离可能会失效。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: