并发编程专题 05 - 并发实战

前言

本节是并发编程专题最后一节。共计5个小节,分别是:

本节重点:

  • CountDownLatch 源码分析
  • Semaphore
  • 原子操作的使用及实现原理
  • 线程池源码分析

CountDownLatch

countdownlatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行。从命名可以解读到countdown是倒数的意思,类似于我们倒计时的概念。

countdownlatch提供了两个方法,一个是countDown,一个是await, countdownlatch初始化的时候需要传入一个整数,在这个整数倒数到0之前,调用了await方法的程序都必须要等待,然后通过countDown来倒数。

使用案例

public class CountDownLatchDemo {
   

    public static void main(String[] args) throws InterruptedException {
   
        CountDownLatch countDownLatch=new CountDownLatch(3);
        new Thread(()->{
   
            try {
   
                Thread.sleep(2000000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
            countDownLatch.countDown();  //递减

        }).start();

        new Thread(()->{
   
            try {
   
                Thread.sleep(2000000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
            countDownLatch.countDown();

        }).start();

        new Thread(()->{
   
            countDownLatch.countDown();
            try {
   
                Thread.sleep(2000000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }).start();

        countDownLatch.await(); //阻塞
        System.out.println("执行完毕 ");
    }
}

从代码的实现来看,有点类似join的功能,但是比join更加灵活。CountDownLatch构造函数会接收一个int类型的参数作为计数器的初始值,当调用CountDownLatch的countDown方法时,这个计数器就会减一。

通过await方法去阻塞去阻塞主流程:

在这里插入图片描述

使用场景

  1. 通过countdownlatch实现最大的并行请求,也就是可以让N个线程同时执行
  2. 比如应用程序启动之前,需要确保相应的服务已经启动,比如我们之前在讲zookeeper的时候,通过原生api连接的地方有用到countDownLatch

源码分析

CountDownLatch类存在一个内部类Sync,上节课我们讲过,它是一个同步工具,一定继承了AbstractQueuedSynchronizer。很显然,CountDownLatch实际上是是使得线程阻塞了,既然涉及到阻塞,就一定涉及到AQS队列

await

await函数会使得当前线程在countdownlatch倒计时到0之前一直等待,除非线程别中断;从源码中可以得知await方法会转发到Sync的acquireSharedInterruptibly方法

public void await() throws InterruptedException {
    
	sync.acquireSharedInterruptibly(1);
}

acquireSharedInterruptibly

这块代码主要是判断当前线程是否获取到了共享锁; 上一节课提到过,AQS有两种锁类型,一种是共享锁,一种是独占锁,在这里用的是共享锁; 为什么要用共享锁,因为CountDownLatch可以多个线程同时通过。

public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
   
        //判断线程是否中断
        if (Thread.interrupted())
            throw new InterruptedException();
        //如果等于0则返回1,否则返回-1,返回-1表示需要阻塞
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

在这里,state的意义是count,如果计数器为0,表示不需要阻塞,否则,只有在满足条件的情况下才会被唤醒

doAcquireSharedInterruptibly

获取共享锁

private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
   
        //创建一个共享模式的节点添加到队列中
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
   
        	//自旋等待共享锁释放,也就是等待计数器等于0。
            for (;;) {
   
            	//获得当前节点的前一个节点
                final Node p = node.predecessor();
                if (p == head) {
   
                	//就判断尝试获取锁
                    int r = tryAcquireShared(arg);
                    //r>=0表示计数器已经归零了,则释放当前的共享锁
                    if (r >= 0) {
   
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值