JAVA之CountDawnLatch并发编程简析
CountDawnLatch是什么?
- countdownlatch 是一个同步类工具,不涉及锁定。
- CountDownLatch是JDK提供的一个同步工具,它可以让一个或多个线程等待,一直等到其他线程中执行完成一组操作。
CountDownLatch有哪些常用的方法?
有countDown方法和await方法,CountDownLatch在初始化时,需要指定用给定一个整数作为计数器。当调用countDown方法时,计数器会被减1;当调用await方法时,如果计数器大于0时,线程会被阻塞,一直到计数器被countDown方法减到0时,线程才会继续执行。计数器是无法重置的,当计数器被减到0时,调用await方法都会直接返回。
举个栗子
比如张三、李四和王约好一起去动物园,等人到齐了在一起进入动物园
- 用户类
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
/**
* @program: concurrent_project
* @description: 张三、李四和王五的用户类
* @author: Wednesday
* @create: 2020-06-9 9:12
**/
public class User implements Runnable{
private CountDownLatch latch;
private String name;
public User(CountDownLatch latch, String name) {
this.latch = latch;
this.name = name;
}
@Override
public void run() {
try {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
Random random = new Random();
System.out.println(sdf.format(new Date())+ " " + name + "出发去动物园");
Thread.sleep((long) (random.nextDouble()*3000)+1000);
System.out.println(sdf.format(new Date()) + " " + name + "到了动物园");
latch.countDown();
}catch (Exception e){
e.printStackTrace();
}
}
}
- 动物园类
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
/**
* @program: concurrent_project
* @description: 动物园类
* @author: Wednesday
* @create: 2020-06-9 9:35
**/
public class Zoo implements Runnable {
private CountDownLatch latch;
private String name;
public Zoo(CountDownLatch latch, String name) {
this.latch = latch;
this.name = name;
}
@Override
public void run() {
try {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
System.out.println(sdf.format(new Date()) + "=========等待所有人到齐");
//等待计数器为0时执行
latch.await();
System.out.println(sdf.format(new Date()) + "=========进入" + name);
}catch (Exception e){
e.printStackTrace();
}
}
}
- 测试类
import com.concurrent.entity.User;
import com.concurrent.entity.Zoo;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* @program: concurrent_project
* @description: 测试类
* @author: Wednesday
* @create: 2020-06-9 9:48
**/
public class CountDawnLatchTester {
public static void main(String[] args) throws InterruptedException {
//初始化CountDownLatch并设置
CountDownLatch latch = new CountDownLatch(3);
List<Thread> threads = new ArrayList<>(3);
threads.add(new Thread(new User(latch, "张三")));
threads.add(new Thread(new User(latch, "李四")));
threads.add(new Thread(new User(latch, "王五")));
for (Thread thread :
threads) {
thread.start();
}
Thread.sleep(100);
new Thread(new Zoo(latch,"动物园")).start();
}
}
- 运行结果
09:49:22.770 王五出发去动物园
09:49:22.784 李四出发去动物园
09:49:22.785 张三出发去动物园
09:49:22.786=========等待所有人到齐
09:49:24.051 张三到了动物园
09:49:24.946 王五到了动物园
09:49:26.555 李四到了动物园
09:49:26.555=========进入动物园
可以看到,动物园类在调用了await()方法之后,一直等到三个人都调用了countDown方法,也就是都到了动物园,才继续执行。
但是如果有一个人在去动物园中途堵车了,其他人又很想快点进入动物园该怎么办呢?
这个时候我们可以调用 await(long timeout, TimeUnit unit) 方法,传入等待的超时时间,设定等待时间为3秒,不管人到没到齐都进入动物园参观
//等待所有人到齐再进入动物园
latch.await();
改为
//不管人到没到齐都进入动物园参观
latch.await(3, TimeUnit.SECONDS);
- 运行结果
10:16:51.555=========等待所有人到齐
10:16:51.556 王五出发去动物园
10:16:51.556 李四出发去动物园
10:16:51.560 张三出发去动物园
10:16:52.955 张三到了动物园
10:16:53.497 李四到了动物园
10:16:54.561=========进入动物园
10:16:54.763 王五到了动物园
可以看到,动物园类在调用了await()方法之后,并没有等到三个人都调用了countDown方法,也就是都到了动物园,才继续执行。
原理
- CountDownLatch有一个内部类叫做Sync,它继承了AbstractQueuedSynchronizer类,其中维护了一个整数state,并且保证了修改state的可见性和原子性。
- 创建CountDownLatch实例时,也会创建一个Sync的实例,同时把计数器的值传给Sync实例
- 在 countDown方法中,只调用了Sync实例的releaseShared方法
- 其中的releaseShared方法,先对计数器进行减1操作,如果减1后的计数器为0,唤醒被await方法阻塞的所有线程
- 其中的tryReleaseShared方法,先获取当前计数器的值,如果计数器为0时,就直接返回;如果不为0时,使用CAS方法对计数器进行减1操作
- 在await方法中,只调用了Sync实例的acquireSharedInterruptibly方法
- 其中acquireSharedInterruptibly方法,判断计数器是否为0,如果不为0则阻塞当前线程
- 其中tryAcquireShared方法,是AbstractQueuedSynchronizer中的一个模板方法,其具体实现在Sync类中,其主要是判断计数器是否为零,如果为零则返回1,如果不为零则返回-1
具体实现可以根据上面的步骤一步一步去查看源码