Java 语言之所以广泛运用于服务端程序,很大一部分原因就是因为在JDK中Java 已经为我们提供了很多并发场景的解决方案,借助这些系统方案我们可以快速应用于具体场景,甚至是在系统方案上进行扩展,这篇文章就好好总结下三种线程控制工具类。
CountDownLatch 是一种允许一个或多个线程阻塞等待,直到在其他线程中的一系列操作完成之后,再继续执行的一次性同步机制(A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes)。简单理解CountDownLatch(内部是基于AbstractQueuedSynchronizer,篇幅问题不在此篇文章分析范围内)就是一个基于信号量机制的阻塞和唤醒线程的工具类,通过调用CountDownLatch提供的api方法就可以达到灵活阻塞和唤醒线程的目的。所谓信号量,其实和引用计数技术中的差不多,所以在初始化的时候需要给这个信号量赋一个数值(一般大于0),每调用CountDownLatch的countDown方法一次,信号量值减 1;若信号量当前值不为0时,调用await()方法的线程会被阻塞,直到count值为0才会继续执行;反之则不会阻塞。另外,CountDownLatch不可复用,计数不能重置。
方法名称 | 说明 |
---|---|
public CountDownLatch(int count) | 通过指定信号量值构造CountDownLatch |
public void await() | 调用await()方法的线程会被阻塞,直到count值为0才继续执行 |
public boolean await(long timeout,TimeUnit unit) | 和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行 |
public void countDown() | 将count值减1 |
众所周知,即使我们几乎同时创建出若干条子线程,且每个子线程内部执行的工作一样,也不能保证每个子线程运行的顺序和结束的时间,因为启动过程和执行过程需要靠cpu 来调度,下面这个例子每次运行的结果也有所不同,但是基本上通过CountDownLatch都能保证主线程在每一组的所有子线程执行完毕之后再继续执行,总之调用await方法时后且当前信号量值不为0 调用线程则会被阻塞,而且一个线程可以被一个或者多个CountDownLatch控制。
public class CountDownLatchDemo {
private final static int GROUP_SIZE = 3;
public static void main(String[] args) throws InterruptedException {
processOneGroup(第一组);
}
/**
*
* @param name
* @throws InterruptedException
*/
private static void processOneGroup(final String name)
throws InterruptedException {
final CountDownLatch mainCountDown = new CountDownLatch(GROUP_SIZE);//用于控制主线程的CountDownLatch
final CountDownLatch startCountDown = new CountDownLatch(1);//
System.out.println("***首先"+Thread.currentThread().getName()+"线程运行:" + "通过循环创建若干工作线程" + "***");
for (int i = 0; i < GROUP_SIZE; i++) {
new Thread(String.valueOf(i)) {
public void run() {
System.out.println(name + "的" + getName() + "线程已经准备就绪");
try {
startCountDown.countDown();
startCountDown.await();//如果startCountDown初始信号量值为1的话经过上一步的coutDown 当前信号量值已经为0 了 即使调用了await也无法阻塞
System.out.println("先调用await方法阻塞"+getName()+ "线程");
System.out.println(getName()+ "线程处理自己的工作ing");
Thread.sleep(3000);
mainCountDown.countDown();//主线程计数减一
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程"+Thread.currentThread().getName() + "运行:"+name + "的" + getName() + "线程已执行完毕");
}
}.start();
}
mainCountDown.await();// 等待所有子线程执行完毕
System.out.println("线程"+Thread.currentThread().getName() + "运行:"+name + "中所有工作线程工作完毕");
//startCountDown.countDown();
System.out.println("----end线程"+Thread.currentThread().getName() + "继续执行------");
}
CyclicBarrier首先从字面上理解是Cyclic Barrier 即循环屏障。CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;而且CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。简而言之,CyclicBarrier可以让一组线程阻塞(暂且把这个状态就叫做barrier)直到barrier状态之后再全部(全部指的是getParties 获取到的所有等待线程)同时执行,当某个线程调用await()方法之后,该线程就处于barrier了。
方法名称 | 说明 |
---|---|
public CyclicBarrier(int parties) | parties:必须要调用await方法的线程数, |
public CyclicBarrier(int parties, Runnable barrierAction) | barrierAction:clicBarrier到达 barrier状态时候要执行才操作 |
public void await() | 调用await()方法的线程会被阻塞,直到CyclicBarrier上所有的线程(Waits until all getParties parties have invoked await on this barrier)都执行了await方法 |
public boolean await(long timeout,TimeUnit unit) | 和await()类似,让这些线程等待至一定的时间,如果还有线程没有到达barrier状态,也直接让到达barrier的线程执行后续任务。 |
public void reset() | 重用CyclicBarrier,将CyclicBarrier状态置为初始态,若当前还有其他线程在阻塞则会抛出BrokenBarrierException异常 |
package concurrent;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
private static final int THREAD_COUNT=3;
/**
* 如果这里parties 不等于THREAD_COUNT会出现问题,可能会一直卡住
*/
private final static CyclicBarrier CYCLIC_BARRIER=new CyclicBarrier(THREAD_COUNT, new Runnable() {
@Override
public void run() {
System.out.println("全部团员已经结束上次行程,导游开始点名,准备进行下一个环节");
}
});
public static void main(String[] args) {
for(int i=0;i<3;i++){
new Thread(String.valueOf(i)){
public void run(){
try {
System.out.println("线程"+getName()+" 已到达旅游地点!");
CYCLIC_BARRIER.await();//阻塞当前线程
System.out.println("线程"+getName()+" 开始骑车");
Thread.sleep(2000);
CYCLIC_BARRIER.await();
System.out.println("线程"+getName()+" 开始爬山");
Thread.sleep(3000);
CYCLIC_BARRIER.await();
System.out.println("线程"+getName()+" 回宾馆休息");
Thread.sleep(1000);
CYCLIC_BARRIER.await();
System.out.println("线程"+getName()+" 开始乘车回家");
CYCLIC_BARRIER.await();
System.out.println("线程"+getName()+" 回家了");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
System.out.print(e.getMessage());
e.printStackTrace();
}
}
}.start();
}
}
}
package concurrent;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class ReuseCyclicBarrier {
public static void main(String[] args) {
final int THREAD_COUNT=3;
CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, new Runnable() {
@Override
public void run() {
print("所有工作线程都已到达barrier状态");
}
});
print("*******第一组 使用 CyclicBarrier*********");
for(int i=0;inew Worker(barrier).start();
}
try {
Thread.sleep(9000);//此处为了保证主线程被阻塞直到第一组3个线程执行完毕所有的工作,所以至少要sleep 3000*3,如果少于则不会等到其他三个线程执行完毕之后才执行主线程
} catch (InterruptedException e) {
e.printStackTrace();
}
print("*************第二组 重用 CyclicBarrier***************\n");
//barrier.reset(); Resets the barrier to its initial state. If any parties are currently
//waiting at the barrier, they will return with a BrokenBarrierException
barrier = new CyclicBarrier(THREAD_COUNT-1, new Runnable() {
@Override
public void run() {
print("【重用】所有工作线程都已到达barrier状态");
}
});
for(int i=0;inew Worker(barrier).start();
}
}
static class Worker extends Thread{
private CyclicBarrier cyclicBarrier;
public Worker(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
doWork(3000L);
print("操作数据完毕,等待其他工作线程"+"cyclicBarrier中的Parties数"+cyclicBarrier.getParties());
cyclicBarrier.await();//只是会阻塞,如果这里耗时很短的话,并不会阻塞主线程
}catch(BrokenBarrierException | InterruptedException e){
e.printStackTrace();
}
print("所有工作线程都已到达barrier状态后,所有线程执行操作完毕,继续处理其他任务...");
}
}
public static void print(String s) {
System.out.println(Thread.currentThread().getName()+" 线程 : "+s);
}
public static void doWork(long mills){
try {
print("正在操作数据ing");
Thread.sleep(mills);//以睡眠来模拟数据操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Semaphore首先从字面上理解是 信号量,Semaphore可以控制同时访问某组资源的线程个数,Semaphore当前在多线程环境下被广泛使用,操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Java 并发库 的Semaphore 可以很轻松完成信号量控制可以通过 acquire() 方法获取一个许可,如果没有就阻塞;而 release() 方法释放一个许可,其实Semaphore和锁机制有点类似,信号量一般用于控制对 某组 资源的访问权限,而锁是控制对 某个 资源的访问权限。
方法名称 | 说明 |
---|---|
public Semaphore(int permits) | permits:一组资源最大同时可以被permits个线程所访问 |
public Semaphore(int permits, boolean fair) | fair:和阻塞队列一样用于标识是否采用公平的访问优先策略,true:按照先来后到的顺序获得机会;false:随机获得优先机会 |
public void acquire() | 获取一个许可,若无许可能够获得,则会一直阻塞,直到获得许可。 |
public void acquire(int permits) | 获取permits个许可 |
public void release() | 释放一个许可,在释放许可之前,必须先获获得许可 |
public void release(int permits) | 释放permits个许可 |
以上四种方法都会阻塞,下面tryXXX的不会阻塞—————— | |
public boolean tryAcquire() | 尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false |
public boolean tryAcquire(long timeout, TimeUnit unit) | 尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false |
public boolean tryAcquire(int permits) | 尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false |
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) | 尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false |
举个例子每一层楼指纹打卡机只有3台,而打卡的员工远不止,那么为了更高效率,只得确保同时只能有3个人能够使用,而当这3个人中的任何一个人打卡完毕离开后,剩下正在等待的其他人中马上又有1个人可以使用了(这个人可以是等待队伍中的任意一个,可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会)这取决于构造Semaphore对象时传入的参数选项。另外单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。
package concurrent;
import java.util.Random;
import java.util.concurrent.Semaphore;
/**
* 假如一个工厂有3台打卡机,但是有6名员工,一台机器同时只能被一个员工使用,只有使用完了,其他员工才能继续使用
* @author Crazy.Mo
*
*/
public class SemaphoreTest {
public static void main(String[] args) {
final int WORKER_NUM = 6; //总员工数
final int SOURCE_COUNT=3;//3台指纹打卡机,一组资源
Semaphore semaphore = new Semaphore(SOURCE_COUNT); //最大同时允许SOURCE_COUNT 个员工使用
for(int i=0;inew FingerPrint(i,semaphore).start();
}
}
static class FingerPrint extends Thread{
private int num;
private Semaphore semaphore;
public FingerPrint(int num,Semaphore semaphore){
this.num = num;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
print("员工"+this.num+"正在使用一台指纹打卡机ing。。。");
Thread.sleep(new Random().nextInt(3000));
print("员工"+this.num+"释放出一台指纹打卡机");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void print(String s) {
System.out.println(Thread.currentThread().getName()+" 线程 : "+s);
}
public static void doWork(long mills){
try {
Thread.sleep(mills);//以睡眠来模拟打卡
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}