zoukankan      html  css  js  c++  java
  • 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();
        }
    }
  • 相关阅读:
    CodeSmith入门教程
    AJAX调用实例
    药品监控增加表结构
    典型SQL 语句总结
    CRM中常用代码
    win2000sever+IIS5不能下载exe文件处理方法
    常用的文件对应的MIME类型
    数据库范式1NF 2NF 3NF BCNF
    多线程编程中如何更改UI值
    NHibernate学习笔记(2)—关系映射
  • 原文地址:https://www.cnblogs.com/kexinxin/p/11679064.html
Copyright © 2011-2022 走看看