JAVA之CountDawnLatch简析

CountDawnLatch是什么?

  1. countdownlatch 是一个同步类工具,不涉及锁定。
  2. 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

具体实现可以根据上面的步骤一步一步去查看源码

本文代码已上传Gitee.

猜你喜欢

转载自blog.csdn.net/weixin_42160515/article/details/106660263