java并发编程系列之CountDownLatch的使用

13 篇文章 0 订阅
8 篇文章 0 订阅

说到定时器,我们就会想到java中的Timer。在jdk1.5中,也提供了一个类似定时器功能的类CountDownLatch,只不过两者有些区别,CountDownLatch类同一时刻只能由一个线程去操作也就是说,在多线程并发下,同时只能由其中的一个线程去操作这个计时器。CountDownLatch 的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如这个想要继续往下执行的任务调用一个 CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的 countDown()方法,这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。看到这里,大家肯定想起了我们前面的那个搞活动的例子,上次是用CyclicBarrier来实现的,下面我们就用CountDownLatch来实现这个例子。示例代码如下:

/**
 * 描述:CountDownLatch的使用示例
 * @author chhliu
 * 创建时间:2015-9-17 下午9:41:37
 */
public class CountDownLatchDemo {
	public static void main(String[] args) throws InterruptedException {
		// 新建一个计数器,初始值为5
		final CountDownLatch latch = new CountDownLatch(5);
		
		/*
		 * 新建5个线程,此处线程数必须和CountDownLatch中设置的倒数个数一致,否则会出问题
		 * 如果此处设置成4的话,就会一直等待,直到第5个线程到来,但没有第5个线程,就会一直死等
		 */
		ExecutorService service = Executors.newFixedThreadPool(5);
		// 提交5个任务
		for (int i = 0; i < 5; i++) {
			// 假设每隔1秒中,有一个人到达
			Thread.sleep(1000);
			service.execute(new Runnable() {
				@Override
				public void run() {
					try {
						System.out.println(Thread.currentThread().getName()
								+ "到达园博园了!");
						// 计数器减1
						latch.countDown();
						// 如果计数器不为0,则线程一直阻塞
						latch.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("所有的人都到达了……");
				}
			});
		}
		// 释放线程池资源
		service.shutdown();
	}
}

测试结果如下:

pool-1-thread-1到达园博园了!
pool-1-thread-2到达园博园了!
pool-1-thread-3到达园博园了!
pool-1-thread-4到达园博园了!
pool-1-thread-5到达园博园了!
所有的人都到达了……
所有的人都到达了……
所有的人都到达了……
所有的人都到达了……
所有的人都到达了……

从测试结果来看,每调用一次countDown方法,则计数器减1,并且线程会被阻塞,知道为0,所有的线程才被唤醒,继续往下走,记住,此处是一次唤醒全部的线程。下面我们来看下这个类的原型:

public class CountDownLatch {
    /**
     * Synchronization control For CountDownLatch.
     * Uses AQS state to represent count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
        Sync(int count) {
            setState(count);
        }
        int getCount() {
            return getState();
        }
        public int tryAcquireShared(int acquires) {
            return getState() == 0? 1 : -1;
        }

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

private final Sync sync;
// 构造方法,同时指定计数器的个数
    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);
}
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回 true 值。如果当前计数大于零,则出于线程调度目的,将阻塞当前线程,且在超时或者被中断之前,该线程将一直处于阻塞状态:
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。 如果当前计数等于零,则不发生任何操作。
    public void countDown() {
        sync.releaseShared(1);
}
// 获取当前剩下的计数个数
    public long getCount() {
        return sync.getCount();
    }
}

前面说到,如果线程数设置成4,而计数器设置成5,那么会发生死等的现象,如果我们不想死等,可以使用await(long timeout, TimeUnit unit)方法,中断被阻塞的线程,使整个流程继续往下走,测试结果如下:

pool-1-thread-1到达园博园了!
pool-1-thread-2到达园博园了!
pool-1-thread-3到达园博园了!
pool-1-thread-4到达园博园了!
所有的人都到达了……
pool-1-thread-1到达园博园了!
所有的人都到达了……
所有的人都到达了……
所有的人都到达了……
所有的人都到达了……

从测试结果来看,pool-1-thread-1这个线程被中断了,并且再一次的被启用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值