深入理解java闭锁实现

0 概述

闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁可以用来确保某些活动直到其他活动都完成后才继续执行。CountDownLatch是一种灵活的闭锁实现,它可以使一个或者多个线程等待一组事件发生。闭锁状态包括一个计数器,该计数器初始化一个正数,表示需要等待的数量。countDown方法递减计数器,表示一个事件已经发生了,而await方法等待计算器达到零,即所有的事件已经完成。如果计数器的值非0,await会一直等待直到计数器为0,或者线程被中断或者超时。

1 使用实例

import java.util.concurrent.CountDownLatch;

/**
 * Created by apple on 17/10/24.
 */
public class CountDownLatchTest {

    public static void main(String[] args) throws Exception {

        //参数count就是事件数(线程数量、计数器)
        final CountDownLatch countDownLatch = new CountDownLatch(2);

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {

                sleepWithNoException(3000);
                System.out.println("任务1 已经完成");
                //count-1
                countDownLatch.countDown();
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {

                sleepWithNoException(1000);
                System.out.println("任务2 已经完成");
                //count-1
                countDownLatch.countDown();
            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //阻塞等待
                    countDownLatch.await();
                    System.out.println("thread3 over");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread3.start();
        thread2.start();
        thread1.start();
        //阻塞等待
        countDownLatch.await();
        System.out.println("任务都已经完成");
    }

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


执行结果:
任务2 已经完成
任务1 已经完成
thread3 over
任务都已经完成

2 CountDownLatch 设计思想

CountDownLatch 是基于AbstractQueuedSynchronizer实现的,AbstractQueuedSynchronizer 中维护了一个队列(双向链表实现),其中结点中除了存放连接前后结点指针外还存放线程信息、线程状态等信息,值得说明是head node 是一个虚节点(只是起到一个连接作用)
这里写图片描述
那么问题来了,线程是如何阻塞以及唤醒的呢?
这里要提出LockSupport类,其有两个重要的方法:

//唤醒线程(底层实现依赖于操作系统)
public static void unpark(Thread thread);
//阻塞当前线程(底层实现依赖于操作系统)
public static void park(Object blocker) 

值得说明的是:unpark函数可以先于park调用。unpark函数为线程提供“许可(permit)”,线程调用park函数则等待“许可”,如果已经有许可,那么线程会马上再继续运行。

public class ParkTest {

    public static void main(String[] args) {
        LockSupport.unpark(Thread.currentThread());
        LockSupport.park(Thread.currentThread());
        System.out.println("线程可以继续执行");
    }
}
//程序会直接运行到结束,不会阻塞

3 CountDownLatch 源码分析

内部类&构造函数


 //继承AbstractQueuedSynchronizer的内部类
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        //基于CAS原子尝试释放共享锁,当线程原子更新为state为0成功时候返回true,其余情况返回false
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

    // 构造函数
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

CountDownLatch类await() ,下面结合源码来分析下是如何阻塞等待线程的

   //await() 方法具体实现
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    //
 public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
            //判断state是否为0 ,如果为0就不用阻塞当前线程了
        if (tryAcquireShared(arg) < 0)

            doAcquireSharedInterruptibly(arg);
    }


    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);
                    //r>=0 表示count=0即等待的线程已经结束
                    if (r >= 0) {
                        //重新设置头结点
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                //设置node状态设置为SIGNAL=-1,并阻塞当前线程(这里由于unpark 可以有先于park 执行,因此不会有并发问题)
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

CountDownLatch类countDown() 方法实现

   public void countDown() {
        sync.releaseShared(1);
    }

   public final boolean releaseShared(int arg) {

           if (tryReleaseShared(arg)) {
           //由于是共享锁,那么release 动作将会传播至各个阻塞节点
            doReleaseShared();
            return true;
        }
        return false;

     */
    private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                        //唤醒后继节点
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            //如果头结点变了,则不退出循环,唤醒线程后会重新设置头节点
            if (h == head)                   // loop if head changed
                break;
        }
    }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值