文章目录
CountDownLatch
CountDownLatch 类是位于 java.util.concurrent 包下的,是一个同步工具类,允许一个或多个线程一起等待其他线程的操作执行完成后再执行相关操作。
CountDownLatch 是基于线程计数器来实现并发访问控制的,主要用于主线程等待其他其他子线程都执行完毕后执行相关操作。
其使用过程为:在主线程中定义 CountDownLatch, 并将线程计数器的初始值设置为子线程的个数,多个子线程并发的执行,每个子线程在执行完毕后都会调用 countDown 函数将计数器的值减 1,直到线程计数器为0,表示所有的子线程任务都已执行完毕,此时在 CountDownLatch 上等待的主线程将被唤醒并继续执行。
我们就可以利用 CountDownLatch 可以实现类似计数器的功能。比如有一个主任务,它要等待其他两个任务都执行完毕之后才能执行,此时就可以利用 CountDownLatch 来实现这种功能。
CountDownLatch中的方法
方法 | 说明 |
---|---|
void await() | 使当前线程进入同步队列进行等待,直到latch的值被减到0或者当前线程被中断,当前线程就会被唤醒。 |
boolean await(long timeout, TimeUnit unit) | 使当前线程等待直到锁存器计数到零为止 |
void countDown() | 减少锁的计数器的计数,如果计数达到零,释放所有等待的线程。 |
String getCount() | 获得latch的数值。 |
CountDownLatch的具体实现
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 让去等待有 3 个三个工作线程执行完成后再执行这个线程,如果不够,就不执行被等待的线程
CountDownLatch c = new CountDownLatch(3);
// 创建 2 个等待线程
WaitThread waitThread1 = new WaitThread("wait-thread-1", c);
WaitThread waitThread2 = new WaitThread("wait-thread-2", c);
// 创建 3 个工作线程
Worker worker1 = new Worker("worker-thread-1", c);
Worker worker2 = new Worker("worker-thread-2", c);
Worker worker3 = new Worker("worker-thread-3", c);
// 创建第 4 个工作线程,而等待线程就不受这个线程锁影响
Worker worker4 = new Worker("worker-thread-4", c);
// 启动所有线程
waitThread1.start();
waitThread2.start();
Thread.sleep(1000);
worker1.start();
Thread.sleep(1000);
worker2.start();
Thread.sleep(1000);
worker3.start();
Thread.sleep(1000);
worker4.start();
}
}
/**
* 等待线程
*/
class WaitThread extends Thread {
private String name;
private CountDownLatch c;
public WaitThread(String name, CountDownLatch c) {
this.name = name;
this.c = c;
}
@Override
public void run() {
try {
// 等待
System.out.println(this.name + " wait...");
c.await();//启动需要等待3个其他线程执行完毕才能执行这个线程
System.out.println(this.name + " continue running...");
System.out.println(this.name + " is end.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 工作线程
*/
class Worker extends Thread {
private String name;
private CountDownLatch c;
public Worker(String name, CountDownLatch c) {
this.name = name;
this.c = c;
}
@Override
public void run() {
System.out.println(this.name + " is running...");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name + " is end.");
c.countDown(); //认为当前线程执行完毕,可以释放需要等待的线程个数,个数减1
}
}
在上面的代码当中就先定义了一个大小为 2 的CountDownLatch, 然后定义了 2 个子线程为等待线程并启动该子线程,在线程执行任务开始前调用 await() 方法,表示当前的子线程需要等待执行 3 个工作线程完才能再执行。 也定义了 4 个子线程为工作线程并启动线程,在线程任务完成后执行countDown() 方法 ,减少一个信号量。当将最开始定义的 3 的信号量全部减去后,就就可以再次启动等待线程继续完成任务,而我们定义的 4 个工作线程,就意味着等待线程是不会礼让成全第 4 个工作线程,完全是各凭本事来。
CyclicBarrier
CyclicBarrier是为循环屏障。它是一个同步工具,可以实现让一组线程等待至某个状态之后再全部同步执行。
在所有等待线程都被释放之后,CyclicBarrier 可以被重用。
CyclicBarrier 的运行状态叫做 Barrier 状态,在调用 await 方法后,线程就处于 Barrier 状态。
说通俗一点就是:是一种线程协作的方法,它可以让一个线程执行到某个点停止,并等待其它线程到达这个点集结完毕再继续运行。
CyclicBarrie中的方法
方法 | 说明 |
---|---|
int await() | 等待所有 线程已经在这个障碍上调用了 await |
int await(long timeout, TimeUnit unit) | 等待所有线程已经在此屏障上调用 await ,或指定的等待时间过去。 |
int getNumberWaiting() | 返回目前正在等待障碍的各方的数量。 |
int getParties() | 返回旅行这个障碍所需的聚会数量。 |
boolean isBroken() | 查询这个障碍是否处于破碎状态。 |
void reset() | 将屏障重置为初始状态。 |
CyclicBarrier 中最重要的方法是 await 方法,它有两种实现方法。
public int await():挂起当前线程直到所有线程都为 Barrier 状态再同时执行后续的任务。
public int await(long timeout,TimeUnit unit) :设置一个超时时间,在超时时间过后,如果还有线程未达到 Barrier 状态,则就不再等待,让达到 Barrier 状态的线程继续执行后续的任务。
CyclicBarrier的具体实现
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CountDownLatchDemo2 {
public static void main(String[] args) throws InterruptedException {
int N = 4;
// 定义 CyclicBarrier
CyclicBarrier barrier = new CyclicBarrier(N);
for (int i = 0; i < N; i++) {
new BusinessThread(barrier).start();
Thread.sleep(1000);
}
}
// 定义业务线程
static class BusinessThread extends Thread {
private CyclicBarrier cyclicBarrier;
public BusinessThread(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
// 执行业务代码
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "执行结束,等待其他线程");
// 业务代码执行结束,等待其他线程成为 Barrier 状态
cyclicBarrier.await();// 当进入 Barrier状态的线程达到数组的时候,就能接着向下执行
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
上面的代码就是先定义了一个 CyclicBarrier,然后循环启动了多个线程,每个线程都是通过构造函数的将 CyclicBarrier 传入线程当中,在线程内部开始执行第 1 阶段的工作,比如:查询数据等;等第一阶段的工作处理完后,再调用 cyclicBarrier.await 方法等待其他线程也完成第1阶段的工作,(CyclicBarrier 让第一组线程等待到达某个状态再一次执行); 等其他线程也执行完第 1 阶段的工作后,便可执行并发操作的下一项任务。
Semaphore
Semaphore指的是信号量,用于控制同时访问某些资源的线程个数,具体事物做法为通过调用 acquire() 来获取一个许可,如果没有许可的话,就等待,在许可使用完后通过 release() 释放该许可,以便其他线程继续使用。
Semaphore 常常被用于多个线程需要共享有限资源的情况。
比如:办公室有两台打印机,但是有 5 个人要使用,一台打印机同时同时一个员工使用,其他员工需要排队等待,且只有该打印机被使用完毕并释放后其他员工才可以使用,这时就可以通过 Semaphore 来实现。
Semaphore中的方法
Semaphore 中常用的几个比较重要的方法:
方法 | 说明 |
---|---|
void acquire() |
以阻塞的方式获取一个许可,在有可用许可时返回该许可,在没有可用许可时阻塞等待,直到获得许可 |
void acquire(int permits) throws InterruptedException |
同时获得多个(permits)许可 |
void acquireUninterruptibly() |
获取一个许可证,在获取到许可证之前线程一直处于阻塞状态(忽略中断)。 |
void acquireUninterruptibly(int permits) |
获得指定数量的许可证,在获取到许可证之前线程一直处于阻塞状态(忽略中断) |
void release() |
释放某个许可 |
void release(int permits) |
释放多个(permits)许可 |
boolean tryAcquire() |
以非阻塞的方式获取一个许可,在有可用许可时获得该许可,就返回 true,否则就是true |
boolean tryAcquire(long timeout,TimeUnit unit) throws InterruptedException |
如果在指定的时间内获取到 permits 个许可,则就返回 true ,否则就是 false |
boolean tryAcquire(int permits) |
如果成功获得到 permits 个许可,则就返回 true,否是就立即返回false。 |
boolean tryAcquire(int permits,long timeout, TimeUnit unit) throws InterruptedException |
如果在指定的时间内成功获取到 permits 个许可,就返回true,否则就是false |
int availablePermits() |
查询可用的许可数量 |
int drainPermits() |
清空许可证,把可用许可证数置为0,返回清空许可证的数量。 |
Semaphore的具体实现
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) throws InterruptedException {
int printNumber = 2;//设置员工数
Semaphore semaphore = new Semaphore(printNumber);
for (int i = 0; i < printNumber + 1; i++) {
new WorkThread("线程" + i + ": ", semaphore).start();
Thread.sleep(500);
}
}
}
/**
* 工作线程
*/
class WorkThread extends Thread {
private String name;
private Semaphore semaphore;
public WorkThread(String name, Semaphore semaphore) {
this.name = name;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
System.out.println(this.name + "尝试获取许可.");
Thread.sleep(2000);
semaphore.acquire();//获取许可,如果没有许可的话,等待许可的出现
System.out.println(this.name + "成功获取到了许可,当前还剩余" + semaphore.availablePermits());
Thread.sleep(4000);
System.out.println(this.name + "准备释放许可");
Thread.sleep(2000);
semaphore.release();//释放许可
System.out.println(this.name + "释放成功,当前还剩余" + semaphore.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在上面的代码里面我们首先定义了数量为 2 的 Semaphore,然后定义了实现 3 个工作线程。在创建线程的时候我们将 Semaphore 传入线程内部。
在调用 acquire() 是开始申请线程许可,如果没有就一直等待,在业务逻辑执行完毕后执行 release() 释放许可,以便让其他线程使用。
执行结果:
CountDownLatch、CyclicBarrier和Semaphore 的区别
CountDownLatch 和 CyclicBarrier 都是用于实现多线程之间的相互等待,但二者的关注点不相同。
CountDownLatch 主要用于主线程等待其他子线程任务均匀执行完毕后再执行接下来的业务逻辑单元(允许一个或多个线程等待一组事件的产生),而 CyclicBarrier 主要用于一组线程互相等待大家都达到某个状态后,再同时执行接下来的业务逻辑单元(等待其他线程运行到栅栏位置)。
此外,CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;
CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得 CyclicBarrier 阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断;
Semaphore 和 Java 中的锁功能比较相似,主要用于控制资源的并发访问。