在JUC包中为我们提供了一个很有用的同步工具类:
- CountDownLatch:闭锁
- CyclicBarrier:屏障
- Semaphore:信号量
1、CountDownLatch(闭锁)
(1)简介
CountDownLatch(闭锁)是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。闭锁可以延迟线程的进度,直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才能继续执行:
- 确保某个计算在其需要的所有资源都被初始化后才能继续执行。
- 确保某个服务在其依赖的所有其他服务都已经启动之后才启动。
- 等待直到某个操作所有参与者都执行完毕其他线程才能继续执行。
(2)使用步骤:
第一步:创建CountDownLatch对象,传入需要等待的线程个数
CountDownLatch countDownLatch=new CountDownLatch(10);
第二步:创建子线程,传入CountDownLatch对象
for(int i=0;i<10;i++){
new MyThread(countDownLatch).start();
}
第三步: 每当一个子线程执行完后,计数器在子线程中减一
countDownLatch.countDown();
(3)示例:
主线程等待指定数量的子线程执行完后开始执行。
package basis.stuJUC.SyncTools;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) {
//创建一个闭锁,传入需要等待线程的数量
CountDownLatch countDownLatch=new CountDownLatch(10);
long start=System.currentTimeMillis();
//创建10个线程
for(int i=0;i<10;i++){
new MyThread(countDownLatch).start();
}
//等待10
try {
System.out.println("开始计时");
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//只有上述创建的十个线程都执行完毕后,主线程才能继续执行。
long end=System.currentTimeMillis();
System.out.println("子线程总用时:"+(end-start));
}
static class MyThread extends Thread{
private CountDownLatch countDownLatch;
public MyThread(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行了...");
int c=0;
for(int i=0;i<9999;i++){
for(int j=0;j<9999;j++){
c=i+j;
}
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"结束了....");
countDownLatch.countDown();
}
}
}
老板等待员工都到达了之后开始开会:
package basis.stuJUC.SyncTools;
import java.util.concurrent.CountDownLatch;
/*
* 所有员工都到达,老板开始开会
*/
public class CountDownLatchDemo2 {
public static void main(String[] args) {
CountDownLatch countDownLatch=new CountDownLatch(5);
Boss mayun=new Boss(countDownLatch);
mayun.start();
for(int i=0;i<5;i++){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Employee(countDownLatch).start();
}
}
//老板类
static class Boss extends Thread{
private CountDownLatch countDownLatch;
public Boss(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println("老板要等待"+countDownLatch.getCount()+"个员工到达才能开始");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有员工都到达了,开始开会.....");
}
}
static class Employee extends Thread{
private CountDownLatch countDownLatch;
public Employee(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"到会议室了...");
countDownLatch.countDown();
}
}
}
如果实际创建的线程的数量多于 CountDownLatch 需要的等待的数量时,一旦等待的线程完成的数量达到指定值,等待的线程就开始立即执行,不会去管其他的线程有没有执行完毕。
2、CyclicBarrier(屏障)
(1)简介
CyclicBarrier和CountDownLatch类似(屏障)表面意思理解为可循环使用的屏障,作用是让一组线程在到达一个屏障时被阻塞,等到最后一个线程到达屏障点,才会运行被拦截的线程一起继续运行。
Cyclic可以模拟高并发,即让指定数量的线程在同一时刻一块执行。
构造函数 CyclicBarrier(int parties) 屏障拦截的线程数量,await() 调用该方法时表示线程已经到达屏障,随即阻塞。
(2)使用步骤
第一步:创建CyclicBarrier(屏障),并指定每次需要拦截线程的数量
CyclicBarrier cyclicBarrier=new CyclicBarrier(10);
第二步:创建子线程,传入CyclicBarrier对象
for(int i=0;i<20;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new MyThread(cyclicBarrier).start();
}
第三步: 在需要多个线程一块执行的代码前,调用CyclicBarrier的等待方法,让当前线程等待。
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
(3)示例
package basis.stuJUC.SyncTools;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
/*
* 简单使用,实现多个线程同时执行
*/
public class CyclicBarrierDemo1 {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier=new CyclicBarrier(10);
for(int i=0;i<20;i++){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
new MyThread(cyclicBarrier).start();
}
}
static class MyThread extends Thread{
private CyclicBarrier cyclicBarrier;
public MyThread(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"准备好了");
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"开始执行了...");
//同时执行的代码
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行完毕...");
}
}
}
上述代码,一共创建了二十个线程,而屏障每次拦截十个线程,让这十个线程一块执行,然后再拦截十个,直到所有的线程都以此方法执行完,所以说CyclicBarrier是可以复用的。
如果创建的线程的总数量不是每次需要拦截线程数量的整数倍,那么最后一次拦截时,屏障前拦截的数量永远达不到屏障的要求,剩下的线程将无法执行,程序也无法结束。
3、Semaphore(信号量)
(1)简介
Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。就这一点而言,单纯的synchronized 关键字是实现不了的。用来控制同时访问特定资源的线程数量,通过协调保证合理的使用公共资源。比如控制某个路口一次只允许10辆车同时通过,或者控制数据库一次只允许100个用户访问。
(2)使用步骤
第一步:在主线程中创建信号量,并设置每次允许并发访问的数量
Semaphore semphore=new Semaphore(5);
第二步:创建线程,传入该信号量
for(int i=0;i<10;i++){
new MyThread(semphore).start();;
}
第三步:对需要控制并发访问数量的资源进行 加锁与解锁
try {
semaphore.acquire();//获取锁
//需要控制并发数量的资源
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
semaphore.release();
}
(3)示例
package basis.stuJUC.SyncTools;
import java.util.concurrent.Semaphore;
/*
* Semphore信号量的使用
*/
public class SemphoreDemo {
public static void main(String[] args) {
Semaphore semphore=new Semaphore(5);
for(int i=0;i<10;i++){
new MyThread(semphore).start();;
}
}
static class MyThread extends Thread{
private Semaphore semaphore;
public MyThread(Semaphore semaphore) {
this.semaphore = semaphore;
}
public static void sayHello(){
System.out.println(Thread.currentThread().getName()+":hello");
}
@Override
public void run() {
try {
semaphore.acquire();//获取锁
sayHello();
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}
}
}
上述代码,创建了十个线程,但是每次只允许五个线程并发访问 sayHello(),如果某个或多个访问sayHello的线程执行完毕,那么等待访问sayHello的线程可以立即补上,但是同时访问的数量不能超过设置的最大值。