coutdownlatch

countdownlatch

AQS

在介绍countdownlatch时首先需要简单介绍一下AQS(AbstractQueuedSychronizer)队列。AQS队列的实现依赖内部的同步队列(FIFO双向队列),如果当前线程获取同步状态失败,AQS会将线程及其等待状态等信息构造成一个Node,将其加入同步队列的尾部,同时阻塞当前线程,当同步状态释放时,唤醒队列的头节点。

AQS主要的三个成员变量

private transient volatile Node head;

private transient volatile Node tail;

private volatile int state;

上面提到的同步状态就是这个int型的变量state。head和tail分别是同步队列的头节点和尾节点。假设state=0表示同步状态可用(如果用于锁,则表示锁可用),state=1表示同步状态已被占用(被锁占用)。

下面说明获取和释放同步状态的过程:

获取同步状态

假设线程A要获取同步状态(这里想象成锁,方便理解),初始状态下state=0,所以线程A可以顺利获取锁,A获取锁后将state置为1。在A没有释放锁期间,线程B也来获取锁,此时因为state=1,表示锁被占用,所以将B的线程信息和等待状态等信息构成出一个Node节点对象,放入同步队列,head和tail分别指向队列的头部和尾部(此时队列中有一个空的Node节点作为头点,head指向这个空节点,空Node的后继节点是B对应的Node节点,tail指向它),同时阻塞线程B(这里的阻塞使用的是LockSupport.park()方法)。后续如果再有线程要获取锁,都会加入队列尾部并阻塞。

释放同步状态

当线程A释放锁时,即将state置为0,此时A会唤醒头节点的后继节点(所谓唤醒,其实是调用LockSupport.unpark(B)方法),即B线程从LockSupport.park()方法返回,此时B发现state已经为0,所以B线程可以顺利获取锁,B获取锁后B的Node节点随之出队。

 

 

Countdownlatch

AQS定义两种资源共享方式:Exclusive(独占、只有一个线程执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

独占式

以RenentrantLock为例,如何知道共享资源是否有线程正在被访问呢?其实,它有一个state变量初始值为0,表示未锁定状态。当线程A访问的时候,state+1,就代表该线程锁定了共享资源,其他线程将无法访问,而当线程A访问完共享资源以后,state-1,直到state等于0,就将释放对共享变量的锁定,其他线程将可以抢占式或者公平式争夺。当然,它支持可重入,那什么是可重入呢?同一线程可以重复锁定共享资源,每锁定一次state+1,也就是锁定多次。说明:锁定多少次就要释放多少次。

共享式

以CountDownLatch为例,共享资源可以被N个线程访问,也就是初始化的时候,state就被指定为N(N与线程个数相等),线程countDown()一次,state会CAS减1,直到所有线程执行完(state=0),那些await()的线程将被唤醒去执行执行剩余动作。

独占式是只唤醒后继节点。共享式是唤醒后继,后继还会去唤醒它的后继,从而实现共享。

 CountDownLatch是一个同步容器,但是有人叫它发令枪,也有人叫它门闩。初始化设定线程的个数,调用countDownLatch.await()阻塞所有线程,直到countDownLatch.countDown()为0,那么将继续执行剩余的操作。

CountDownLatch的应用场景

  1. 主线程等待子线程执行完成在执行
  2. 并行执行多任务

 

主线程等待子线程执行完成在执行

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 主线程等待子线程执行完成再执行
 */
public class CountdownLatchTest1 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(3);
        final CountDownLatch latch = new CountDownLatch(3);
        for (int i = 0; i < 3; i++) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("子线程" + Thread.currentThread().getName() + "开始执行");
                        Thread.sleep((long) (Math.random() * 10000));
                        System.out.println("子线程"+Thread.currentThread().getName()+"执行完成");
                        latch.countDown();//当前线程调用此方法,则计数减一
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            service.execute(runnable);
        }

        try {
            System.out.println("主线程"+Thread.currentThread().getName()+"等待子线程执行完成...");
            latch.await();//阻塞当前线程,直到计数器的值为0
            System.out.println("主线程"+Thread.currentThread().getName()+"开始执行...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

并行执行多任务

百米赛跑,4名运动员选手到达场地等待裁判口令,裁判一声口令,选手听到后同时起跑,当所有选手到达终点,裁判进行汇总排名

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountdownLatchTest2 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        final CountDownLatch cdOrder = new CountDownLatch(1);
        final CountDownLatch cdAnswer = new CountDownLatch(4);
        for (int i = 0; i < 4; i++) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("选手" + Thread.currentThread().getName() + "正在等待裁判发布口令");
                        cdOrder.await();
                        System.out.println("选手" + Thread.currentThread().getName() + "已接受裁判口令");
                        Thread.sleep((long) (Math.random() * 10000));
                        System.out.println("选手" + Thread.currentThread().getName() + "到达终点");
                        cdAnswer.countDown();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            service.execute(runnable);
        }
        try {
            Thread.sleep((long) (Math.random() * 10000));
            System.out.println("裁判"+Thread.currentThread().getName()+"即将发布口令");
            cdOrder.countDown();
            System.out.println("裁判"+Thread.currentThread().getName()+"已发送口令,正在等待所有选手到达终点");
            cdAnswer.await();
            System.out.println("所有选手都到达终点");
            System.out.println("裁判"+Thread.currentThread().getName()+"汇总成绩排名");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        service.shutdown();
    }
}

 

posted on 2019-10-15 17:24  kexinxin  阅读(357)  评论(0编辑  收藏  举报

导航