同步工具类之CountDownLatch

同步工具类之CountDownLatch

    • 1.CountDownLatch的作用
    • 2.CountDownLatch的使用
    • 3.countDownLatch的原理
      • 3.1 countDown原理
      • 3.2 await原理
    • 4.CountDownLatch与CyclicBarrier的区别

1.CountDownLatch的作用

CountDownLatch的作用很简单,就是一个或者一组线程在开始执行操作之前,必须要等到其他线程执行完才可以。 我们举一个例子来说明,在考试的时候,老师必须要等到所有人交了试卷才可以走。此时老师就相当于等待线程,而学生就好比是执行的线程。

简而言之,CountDownLatch有一个计数器字段,您可以根据需要减少它。然后我们可以用它来阻塞一个调用线程,直到它被计数到零。

2.CountDownLatch的使用

public class CountDownLatchTest {

	public static CountDownLatch countDownLatch = new CountDownLatch(7); 
	public static void main(String[] args) {
		
		for (int i = 0; i < 7; i++) {
			int index = i;
			new Thread(() -> {
				System.out.println("第"+(index+1)+"个人已经到达");
				countDownLatch.countDown();	//countDownLatch计数器减一
				
			}).start();
		}
		try {
			countDownLatch.await();	//将主线程阻塞,直到其他线程执行完毕,countDownLatch计数器为0
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("所有人都已经到达");
	}
}

输出:
同步工具类之CountDownLatch_第1张图片

如果上面的程序不使用countDownLatch会得到什么结果?

public class CountDownLatchTest {

	public static void main(String[] args) {
		
		for (int i = 0; i < 7; i++) {
			int index = i;
			new Thread(() -> {
				System.out.println("第"+(index+1)+"个人已经到达");
				
			}).start();
		}
		System.out.println("所有人都已经到达");
	}
}

输出:
同步工具类之CountDownLatch_第2张图片
如果不使用countDownLatch就有可能会使主线程先执行,再执行其他线程。所以countDownLatch通常用于保证某一线程在其他线程执行完毕,再运行当前线程。

3.countDownLatch的原理

3.1 countDown原理

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

CountDownLatch里面保存了一个count值,通过减1操作,直到为0时候,等待线程才可以执行。调用了sync的releaseShared方法进行减一。

sync是什么,releaseShared方法又是如何实现的?

sync继承了AbstractQueuedSynchronizer(AQS)。AQS的其中一个作用就是维护线程状态和获取释放锁。在这里也就是说CountDownLatch使用AQS机制维护锁状态。而releaseShared(1)方法就是释放了一个共享锁。底层使用AQS机制调用releaseShared方法释放一个锁资源。

3.2 await原理

两个方法都是让线程等待,一个没有时间限制,一个有时间限制

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

(1)无时间限制的 await方法

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
    
    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }
    
    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);
        }
    }
  • 首先第一个判断是否被中断,如果被中断了,那就抛出中断异常。
  • 然后判断当前是否还有线程未执行,如果有那就,那就执行doAcquireSharedInterruptibly方法继续等待。
  • tryAcquireShared是AQS里的方法,表示countDown是否减少到0,如果没有则返回-1,如果有则返回0
  • 如果还有线程未执行,调用doAcquireSharedInterruptibly方法,他会用一个一个的节点将线程串起来 等达到条件后再一个一个的唤醒。这里面也使用了CAS机制,而且就是使用链表穿起来的。

(2)有时间限制的 wait方法

等待指定的时间,如果还有线程没执行完,那就接着执行。

    public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquireShared(arg) >= 0 ||
            doAcquireSharedNanos(arg, nanosTimeout);
    }

    private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        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 true;
                    }
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • 如果还有现场未执行,就执行doAcquireSharedNanos方法
  • 如果当前还有线程未执行而且过了超时时间,那就直接执行等待线程就好了,不再等了。也就是我在指定的时间内你没执行完我等着你,要是超了这个时间点我就不管了。

4.CountDownLatch与CyclicBarrier的区别

java中还有一个同步工具类叫做CyclicBarrier,他的作用和CountDownLatch类似。同样是等待其他线程都完成了,才可以进行下一步操作。

我们看一下区别:
CountDownLatch: 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行
CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。
关键点其实就在于那N个线程
(1)CountDownLatch里面N个线程就是学生,学生做完了试卷就可以走了,不用等待其他的学生是否完成
(2)CyclicBarrier 里面N个线程就是所有的游戏玩家,一个游戏玩家加载到100%还不可以,必须要等到其他的游戏玩家都加载到100%才可以开局

CountDownLatch的缺点?

CountDownLatch看起来虽然很好用,也有很多不足之处,比如说CountDownLatch是一次性的 , 计数器的值只能在构造方法中初始化一次 , 之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后 , 它不能再次被使用。

你可能感兴趣的:(java并发)