马士兵-郑金维—并发编程—6.JUC并发工具

JUC并发工具

一、CountDownLatch应用&源码分析

1.1 CountDownLatch介绍

CountDownLatch就是JUC包下的一个工具,整个工具最核心的功能就是计数器。

如果有三个业务需要并行处理,并且需要知道三个业务全部都处理完毕了。

需要一个并发安全的计数器来操作。

CountDownLatch就可以实现。

给CountDownLatch设置一个数值。可以设置3。

每个业务处理完毕之后,执行一次countDown方法,指定的3每次在执行countDown方法时,对3进行-1。

主线程可以在业务处理时,执行await,主线程会阻塞等待任务处理完毕。

当设置的3基于countDown方法减为0之后,主线程就会被唤醒,继续处理后续业务。

马士兵-郑金维—并发编程—6.JUC并发工具_第1张图片

当咱们的业务中,出现2个以上允许并行处理的任务,并且需要在任务都处理完毕后,再做其他处理时,可以采用CountDownLatch去实现这个功能。

1.2 CountDownLatch应用

模拟有三个任务需要并行处理,在三个任务全部处理完毕后,再执行后续操作

CountDownLatch中,执行countDown方法,代表一个任务结束,对计数器 - 1

执行await方法,代表等待计数器变为0时,再继续执行

执行await(time,unit)方法,代表等待time时长,如果计数器不为0,返回false,如果在等待期间,计数器为0,方法就返回true

一般CountDownLatch更多的是基于业务去构建,不采用成员变量。

static ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(3);

static CountDownLatch countDownLatch = new CountDownLatch(3);

public static void main(String[] args) throws InterruptedException {
   
    System.out.println("主业务开始执行");
    sleep(1000);
    executor.execute(CompanyTest::a);
    executor.execute(CompanyTest::b);
    executor.execute(CompanyTest::c);
    System.out.println("三个任务并行执行,主业务线程等待");
    // 死等任务结束
    // countDownLatch.await();
    // 如果在规定时间内,任务没有结束,返回false
    if (countDownLatch.await(10, TimeUnit.SECONDS)) {
   
        System.out.println("三个任务处理完毕,主业务线程继续执行");
    }else{
   
        System.out.println("三个任务没有全部处理完毕,执行其他的操作");
    }
}

private static void a() {
   
    System.out.println("A任务开始");
    sleep(1000);
    System.out.println("A任务结束");
    countDownLatch.countDown();
}
private static void b() {
   
    System.out.println("B任务开始");
    sleep(1500);
    System.out.println("B任务结束");
    countDownLatch.countDown();
}
private static void c() {
   
    System.out.println("C任务开始");
    sleep(2000);
    System.out.println("C任务结束");
    countDownLatch.countDown();
}

private static void sleep(long timeout){
   
    try {
   
        Thread.sleep(timeout);
    } catch (InterruptedException e) {
   
        e.printStackTrace();
    }
}

1.3 CountDownLatch源码分析

保证CountDownLatch就是一个计数器,没有什么特殊的功能,查看源码也只是查看计数器实现的方式

发现CountDownLatch的内部类Sync继承了AQS,CountDownLatch就是基于AQS实现的计数器。

AQS就是一个state属性,以及AQS双向链表

猜测计数器的数值实现就是基于state去玩的。

主线程阻塞的方式,也是阻塞在了AQS双向链表中。

1.3.1 有参构造

就是构建内部类Sync,并且给AQS中的state赋值

// CountDownLatch的有参构造
public CountDownLatch(int count) {
   
    // 健壮性校验
    if (count < 0) throw new IllegalArgumentException("count < 0");
    // 构建内部类,Sync传入count
    this.sync = new Sync(count);
}

// AQS子类,Sync的有参构造
Sync(int count) {
   
    // 就是给AQS中的state赋值
    setState(count);
}
1.3.2 await方法

await方法就时判断当前CountDownLatch中的state是否为0,如果为0,直接正常执行后续任务

如果不为0,以共享锁的方式,插入到AQS的双向链表,并且挂起线程

// 一般主线程await的方法,阻塞主线程,等待state为0
public void await() throws InterruptedException {
   
    sync.acquireSharedInterruptibly(1);
}

// 执行了AQS的acquireSharedInterruptibly方法
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
   
    // 判断线程是否中断,如果中断标记位是true,直接抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        // 共享锁挂起的操作
        doAcquireSharedInterruptibly(arg);
}

// tryAcquireShared在CountDownLatch中的实现
protected int tryAcquireShared(int acquires) {
   
    // 查看state是否为0,如果为0,返回1,不为0,返回-1
    return (getState() == 0) ? 1 : -1;
}

private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
   
    // 封装当前先成为Node,属性为共享锁
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
   
        for (;;) {
   
            final Node p = node.predecessor();
            if (p == head) {
   
                int r = tryAcquireShared(arg);
                if (r >= 0) {
   
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // 在这,就需要挂起当前线程。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
   
        if (failed)
            cancelAcquire(node);
    }
}
1.3.3 countDown方法

countDown方法本质就是对state - 1,如果state - 1后变为0,需要去AQS的链表中唤醒挂起的节点

// countDown对计数器-1
public void countDown() {
   
    // 是-1。
    sync.releaseShared(1);
}

// AQS提供的功能
public final boolean releaseShared(int arg) {
   
    // 对state - 1
    if (tryReleaseShared(arg)) {
   
        // state - 1后,变为0,执行doReleaseShared
        doReleaseShared();
        return true;
    }
    return false;
}
// CountDownLatch的tryReleaseShared实现
protected boolean tryReleaseShared(int releases) {
   
    // 死循环是为了避免CAS并发问题
    for (;;) {
   
        // 获取state
        int c = getState();
        // state已经为0,直接返回false
        if (c == 0)
            return false;
        // 对获取到的state - 1
        int nextc = c-1;
        // 基于CAS的方式,将值赋值给state
        if (compareAndSetState(c, nextc))
            // 赋值完,发现state为0了。此时可能会有线程在await方法处挂起,那边挂起,需要这边唤醒
            return nextc == 0;
    }
}

// 如何唤醒在await方法处挂起的线程
private void doReleaseShared() {
   
    // 死循环
    for (;;) {
   
        // 拿到head
        Node h = head;
        // head不为null,有值,并且head != tail,代表至少2个节点
        // 一个虚拟的head,加上一个实质性的Node
        if (h != null && h != tail) {
   
            // 说明AQS队列中有节点
            int ws = h.waitStatus;
            // 如果head节点的状态为 -1.
            if (ws == Node.SIGNAL) {
   
                // 先对head节点将状态从-1,修改为0,避免重复唤醒的情况
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;  
                // 正常唤醒节点即可,先看head.next,能唤醒就唤醒,如果head.next有问题,从后往前找有效节点
                unparkSuccessor(h);
            }
            // 会在Semaphore中谈到这个位置
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;  
        }
        // 会在Semaphore中谈到这个位置
        if (h == head)  
            break;
    }
}

二、CyclicBarrier应用&源码分析

2.1 CyclicBarrier介绍

从名字上来看CyclicBarrier,就是代表循环屏障

Barrier屏障:让一个或多个线程达到一个屏障点,会被阻塞。屏障点会有一个数值,当达到一个线程阻塞在屏障点时,就会对屏障点的数值进行-1操作,当屏障点数值减为0时,屏障就会打开,唤醒所有阻塞在屏障点的线程。在释放屏障点之后,可以先执行一个任务,再让所有阻塞被唤醒的线程继续之后后续任务。

Cyclic循环:所有线程被释放后,屏障点的数值可以再次被重置。

CyclicBarrier一般被称为栅栏。

CyclicBarrier是一种同步机制,允许一组线程互相等待。现成的达到屏障点其实是基于await方法在屏障点阻塞。

CyclicBarrier并没有基于AQS实现,他是基于ReentrantLock锁的机制去实现了对屏障点–,以及线程挂起的操作。(CountDownLatch本身是基于AQS,对state进行release操作后,可以-1)

CyclicBarrier没来一个线程执行await,都会对屏障数值进行-1操作,每次-1后,立即查看数值是否为0,如果为0,直接唤醒所有的互相等待线程。

CyclicBarrier对比CountDownLatch区别

  • 底层实现不同。CyclicBarrier基于ReentrantLock做的。CountDownLatch直接基于AQS做的。
  • 应用场景不同。CountDownLatch的计数器只能使用一次。而CyclicBarrier在计数器达到0之后,可以重置计数器。CyclicBarrier可以实现相比CountDownLatch更复杂的业务,执行业务时出现了错误,可以重置CyclicBarrier计数器,再次执行一次。
  • CyclicBarrier还提供了很多其他的功能:
    • 可以获取到阻塞的现成有多少
    • 在线程互相等待时,如果有等待的线程中断,可以抛出异常,避免无限等待的问题。
  • CountDownLatch一般是让主线程等待,让子线程对计数器–。CyclicBarrier更多的让子线程也一起计数和等待,等待的线程达到数值后,再统一唤醒

CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再一次执行。

2.2 CyclicBarrier应用

出国旅游。

导游小姐姐需要等待所有乘客都到位后,发送护照,签证等等文件,再一起出发

比如Tom,Jack,Rose三个人组个团出门旅游

在构建CyclicBarrier可以指定barrierAction,可以选择性指定,如果指定了,那么会在barrier归0后,优先执行barrierAction任务,然后再去唤醒所有阻塞挂起的线程,并行去处理后续任务。

所有互相等待的线程,可以指定等待时间,并且在等待的过程中,如果有线程中断,所有互相的等待的线程都会被唤醒。

如果在等待期间,有线程中断了,唤醒所有线程后,CyclicBarrier无法继续使用。

如果线程中断后,需要继续使用当前的CyclicBarrier,需要调用reset方法,让CyclicBarrier重置。


如果CyclicBarrier的屏障数值到达0之后,他默认会重置屏障数值,CyclicBarrier在没有线程中断时,是可以重复使用的。

public static void main(String[] args) throws InterruptedException {
   
    CyclicBarrier barrier = new CyclicBarrier(3,() -> {
   
        System.out.println("等到各位大佬都到位之后,分发护照和签证等内容!");
    });

    new Thread(() -> {
   
        System.out.println("Tom到位!!!");
        try {
   
            barrier.await();
        } catch (Exception e) {
   
            System.out.println("悲剧,人没到齐!");
            return;
        }
        System.out.println("Tom出发!!!");
    }).start();
    Thread.sleep(100);
    new Thread(() -> {
   
        System.out.println("Jack到位!!!");
        try {
   
            barrier.await();
        } catch (Exception e) {
   
            System.out.println("悲剧,人没到齐!");
            return;
        }
        System.out.println("Jack出发!!!");
    }).start();
    Thread.sleep(100);
    new Thread(() -> {
   
        System.out.println("Rose到位!!!");
        try {
   
            barrier.await();
        } catch (Exception e) {
   
            System.out.println("悲剧,人没到齐!");
            return;
        }
        System.out.println("Rose出发!!!");
    }).start();
    /*
    tom到位,jack到位,rose到位
    导游发签证
    tom出发,jack出发,rose出发
     */

}

2.3 CyclicBarrier源码分析

分成两块内容去查看,首先查看CyclicBarrier的一些核心属性,然后再查看CyclicBarrier的核心方法

2.3.1 CyclicBarrier的核心属性
public class CyclicBarrier {
   
   // 这个静态内部类是用来标记是否中断的
    private static class Generation {
   
        boolean broken = false;
    }

    /** CyclicBarrier是基于ReentrantLock实现的互斥操作,以及计数原子性操作 */
    private final ReentrantLock lock = new ReentrantLock();
    /** 基于当前的Condition实现线程的挂起和唤醒 */
    private final Condition trip = lock.newCondition();
    /** 记录有参构造传入的屏障数值,不会对这个数值做操作 */
    private final int parties;
    /** 当屏障数值达到0之后,优先执行当前任务  */
    private final Runnable barrierCommand;
    /** 初始化默认的Generation,用来标记线程中断情况 */
    private Generation generation = new Generation();
    /** 每来一个线程等待,就对count进行-- */
    private int count;
}
2.3.2 CyclicBarrier的有参构造

掌握构建CyclicBarrier之后,内部属性的情况

// 这个是CyclicBarrier的有参构造
// 在内部传入了parties,屏障点的数值
// 还传入了barrierAction,屏障点的数值达到0,优先执行barrierAction任务
public CyclicBarrier(int parties, Runnable barrierAction) {
   
    // 健壮性判
    if (parties 

你可能感兴趣的:(并发编程,多线程与高并发,juc)