[置顶] JDK-CountDownLatch-实例、源码和模拟实现

Conception

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
根据CountDownLatch的document,就是说CountDownLatch作为一个同步的助手,可以阻塞一个线程,等待另一个线程结束了再执行。
所以CountDownLatch的作用在于:通过阻塞来协调线程间的执行顺序。
CountDownLatch最重要的方法为await()和countDown()。首先,初始化指定一个count,每次countDown()都会count--,而await()就是阻塞调用它的方法,直到count==0。
所以可以把CountDownLatch看成同步的计数器,什么时候倒数到0就执行,否则阻塞。

Demo

我们首先做一个CountDownLatch的demo,看看它是怎么一个阻塞。
count初始化为2,所以count.await()会阻塞主线程,直到两个processor都countDown(),即count==0的时候才会执行,打印出"Processors has been done."

public class CountDownLatchTest {
	public static void main(String[] args) throws InterruptedException {
		CountDownLatch count = new CountDownLatch(2);
		ExecutorService exc = Executors.newSingleThreadExecutor();
		
		System.out.println("Before submit the processor.");
		exc.submit(new CountProcessor(count, "P1"));
		exc.submit(new CountProcessor(count, "P2"));
		System.out.println("Has submited the processor.");

		count.await();
		System.out.println("Processors has been done.");
		exc.shutdown();
	}
}

class CountProcessor extends Thread {
	private CountDownLatch count;
	private String name;
	public CountProcessor(CountDownLatch count, String name) {
		this.count = count;
		this.name = name;
	}
	@Override
	public void run() {
		try {
			Thread.currentThread().sleep(1000);
			System.out.println(this.name + " has been done.");
			count.countDown();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}



修改一下上面的程序,我们可以模拟一个百米赛跑的现场:
首先运动员进场:前期数据准备和线程池的初始化。
准备工作完成后统一起跑:传入的begin.await()一直阻塞着Runner.run(),在前期准备完成后,begin.countDown()后begin.count==0,阻塞结束,Runner起跑。
比赛一直进行,直到所有选手冲过终点:end.await()会阻塞主线程,所以最后的那条print语句不会提前打印,而是等待Runner.run()结束执行end.countDown(),减少3次直到end.count==0才接触阻塞,宣布比赛结束。
这就是CountDownLatch的经典场景,统一开始,统一结束。

public class CountDownLatchTest {
	public static void main(String[] args) throws InterruptedException {
		CountDownLatch begin = new CountDownLatch(1);
		CountDownLatch end = new CountDownLatch(3);
		ExecutorService exc = Executors.newCachedThreadPool();
		
		System.out.println("Runners are comming.");
		exc.submit(new CountProcessor(begin, end, "Runner_1", 1000));
		exc.submit(new CountProcessor(begin, end, "Runner_2", 2000));
exc.submit(new CountProcessor(begin, end, "Runner_3", 3000));
		System.out.println("Ready.");
		
		Thread.currentThread().sleep(1000);
		System.out.println("Go.");
		begin.countDown();
		
		end.await();
		System.out.println("All runners Finish the match.");
		exc.shutdown();
	}
}

class CountProcessor extends Thread {
	private CountDownLatch beginCount;
	private CountDownLatch endCount;
	private String name;
	private int runningTime;
	public CountProcessor(CountDownLatch beginCount, CountDownLatch endCount, String name, int runningTime) {
		this.beginCount = beginCount;
		this.endCount = endCount;
		this.name = name;
		this.runningTime = runningTime;
	}

	@Override
	public void run() {
		try {
			this.beginCount.await();
			System.out.println(this.name + " start.");
			Thread.currentThread().sleep(this.runningTime);
			System.out.println(this.name + " breast the tape.");
			this.endCount.countDown();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}


Source Code

我们来看JDK中CountDownLatch是怎么定义的,以及其中的主要方法。

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

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

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

public long getCount() {
        return sync.getCount();
}
}



很显然,CountDownLatch的逻辑都封装在内部类Sync中,这也是通过装饰者模式来提高封装性的普遍做法。
Sync继承了AbstractQueuedSynchronizer,这里的调用关系有点麻烦,我就不一一展开了,我贴了部分关键的代码(如下)。
概括一下,CountDownLatch的count存放在以volatile声明的变量state。在await()的时候for轮询state,一直轮询以达到阻塞的效果,直到state==0。而countDown()就是通过CompareAndSet的方式来递减state。

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;
        }

        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 abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
private volatile int state;
private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        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);
        }
}
}


Simulator

从上面可以看出来,其实CountDownLatch的实现很简单,countDown的时候原子修改,await的时候循环判断volatile来阻塞。
所以完全可以通过原子类来实现,而且还更加beautiful。以下,我用AtomicInteger来模拟CountDownLatch的实现。
测试程序还是和上面的赛跑程序一样的,只是把CountDownLatch替换成了CountDownLatchSimulator。
最后测试结果还是一样的,只是过程简单了许多,因为JDK把很多防止冲突的逻辑都封装在原子类了。其中await的时候,可以在循环中加上sleep,能减低系统轮询的消耗。
我只是做了一个简单的实现,至于性能和安全性方面,还希望大神们指导大笑

public class CountDownLatchSimulator {
	private AtomicInteger count;
	public CountDownLatchSimulator(int count) {
		this.count = new AtomicInteger(count);
	}
	
	public void await() throws InterruptedException {
		while (this.count.get() != 0) {
//			Thread.currentThread().sleep(100);
		}
	}
	
	public void countDown() {
		this.count.getAndDecrement();
	}
	
	public static void main(String[] args) throws InterruptedException {
		CountDownLatchSimulator begin = new CountDownLatchSimulator(1);
		CountDownLatchSimulator end = new CountDownLatchSimulator(3);
		ExecutorService exc = Executors.newCachedThreadPool();
		
		System.out.println("Runners are comming.");
		exc.submit(new CountProcessor2(begin, end, "Runner_1", 1000));
		exc.submit(new CountProcessor2(begin, end, "Runner_2", 2000));
		exc.submit(new CountProcessor2(begin, end, "Runner_3", 3000));
		System.out.println("Ready.");
		
		Thread.currentThread().sleep(2000);
		System.out.println("Go.");
		begin.countDown();
		
		end.await();
		System.out.println("All runners Finish the match.");
		exc.shutdown();
	}
}

class CountProcessor2 extends Thread {
	
	private CountDownLatchSimulator beginCount;
	private CountDownLatchSimulator endCount;
	private String name;
	private int runningTime;
	public CountProcessor2(CountDownLatchSimulator beginCount, CountDownLatchSimulator endCount, String name, int runningTime) {
		this.beginCount = beginCount;
		this.endCount = endCount;
		this.name = name;
		this.runningTime = runningTime;
	}
	
	@Override
	public void run() {
		try {
			this.beginCount.await();
			System.out.println(this.name + " start.");
			Thread.currentThread().sleep(this.runningTime);
			System.out.println(this.name + " breast the tape.");
			this.endCount.countDown();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
posted @ 2013-09-15 20:31  pangbangb  阅读(183)  评论(0编辑  收藏  举报