Java并发之CountDownLatch

    在并发操作中,当需要当前线程c等待另一线程a结束后在运行的话,我们首先想到的是join方法,在c线程运行中调用a.join(),该方法会使当前线程阻塞于a,直到线程a运行结束,JVM调用a.notifyAll()方法唤醒z。

    在Java1.5之后,并发包提供的CountDownLatch也可以实现join功能,并且更为强大。

一、使用方法

    下面放出一个简单的Demo:

public static void main(String[]args){
		CountDownLatch c = new CountDownLatch(2);
		Thread a = new Thread(new Runnable(){

			@Override
			public void run() {
				try {
					Thread.currentThread().sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("a");
				c.countDown();
			}
				
		});
		Thread b = new Thread(new Runnable(){

			@Override
			public void run() {
				try {
					Thread.currentThread().sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("b");
				c.countDown();
			}
				
		});
		a.start();
		b.start();
		try {
			c.await();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("main");
	}
/*
a
b
main
*/

    CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完成,这里就传入N。每当调用CountDownLatch的countDown方法,N就会减1。CountDownLatch的await方法会阻塞当前线程,知道N变为0。由于countDown方法可以用在任何地方,所以既可以是N个线程, 也可以是1个线程N个步骤。在使用多线程时,只需要把这个CountDownLatch的引用传入线程即可。此外,CountDownLatch也允许多个线程调用await方法,同时阻塞多个线程。

    还有一点要注意的是,CountDownLatch的计数器无法被重置。

二、实现原理

    同读写锁一样,CountDownLatch本质上也是一个共享锁。它允许一个或多个线程等待其他线程。它的实验原理也是同读写锁一样,通过队列同步器(AbstractQueuedSynchronizer AQS)实现的:

    构造方法:

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

    构造器传入的计数count被用来构造一个Sync(AQS)对象。Sync的构造函数如下:

       Sync(int count) {
            setState(count);	//设置同步状态
        }

    这里,同步器的state就是一个锁状态,当state大于0时,锁被占用,阻塞的线程就不能被唤醒。

    阻塞方法:

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    这里调用了同步器的共享式获取同步状态,如果当前线程未获取同步,则会进入同步队列等待。并且此方法响应中断。

    同步器中该方法源码如下:

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();	//响应中断
        if (tryAcquireShared(arg) < 0)		//尝试获取共享锁 
            doAcquireSharedInterruptibly(arg);	//尝试失败 线程进入阻塞
    }

    该方法为模板方法,关键在于重写的tryAcquireShared方法,如下:

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

    尝试获取共享锁,当锁计数器==0,即锁是可以获取的,则返回1,获取锁成功,直接退出acquireSharedInterruptibly方法。否则返回-1,进入doAcquireSharedInterruptibly方法阻塞当前线程。doAcquireSharedInterruptibly方法源码如下:

    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);	//创建当前线程的Node节点,且标记为共享锁,将锁加入CLH队列末尾
        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);
        }
    }
  

    countDown方法

    public void countDown() {
        sync.releaseShared(1);
    }
    实际上调用队列同步器的释放锁方法:
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;


    首先会尝试直接释放共享锁,成功则直接返回,否则调用doReleaseShared();

        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;			//状态-1
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }


三、总结

    CountDownLatch是通过共享锁实现的。在创建CountDownLatch中时,会传递一个int类型参数count,该参数是队列同步器中的state状态值,表示共享锁最多能被count线程同时获取。当某线程调用该CountDownLatch的await()方法时,该线程会等待共享锁可用时,才能获取共享锁。而共享锁的可用条件就是state等于0。只有每次调用CountDownLatch的countDown方法,状态值才会减1。


 

 
 

猜你喜欢

转载自blog.csdn.net/u010771890/article/details/74908797