`
春花秋月何时了
  • 浏览: 41792 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

Java并发包同步辅助器CountDownLatch

 
阅读更多

引言及简介

前面我们介绍了独占锁ReentrantLock实现的一个同步辅助工具CyclicBarrier, 它能够使一组线程互相等待,今天我们介绍另一种同步辅助器CountDownLatch,它其实可以看着是利用共享锁实现的,只不过它没有使用到类似共享锁Semaphore那么复杂的逻辑,所以它的实现没有直接利用Semaphore完成,而是直接在AQS的共享式获取/释放同步资源的基础上实现的一个非常简单的同步辅助工具。

 

根据Java Doc的描述,CountDownLatch可以在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。类比CyclicBarrier的一组线程之间的相互等待,CountDownLatch则是一个或一组线程等待另一个或另一组线程,所以CountDownLatch与CyclicBarrier各自等待的线程是不同的。并且CountDownLatch内部维护的计数器是不能重置循环使用的,而CyclicBarrier的计数器是可以被重置循环使用的。

 

通俗的讲,在用给定的计数初始化CountDownLatch之后,所有执行了await()方法的线程必须要等待其他线程执行相应次数的countDown()之后才会返回,否则将一直阻塞。相当于CountDownLatch维护了一个计数器,执行了await()方法的线程必须要等到计数器清零之后才会返回,而其他线程每执行一次countDown(),计数器就会减1,所以其实执行await()方法的线程可以是多个,而执行countDown()的线程也可以是一个线程的多次执行,因为countDown()方法的执行不存在阻塞等待的情况。

 

使用示例

    假设某个主任务在执行过程中,分别需要满足2个条件才能继续往下执行,这两个条件分别由另外两个线程去满足:

final CountDownLatch latch = new CountDownLatch(2);
		
new Thread(){
	public void run() {
		try {
		   System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
		   Thread.sleep(3000);
		   System.out.println("主线程"+Thread.currentThread().getName()+"达成主线程的条件1");
		   latch.countDown();
		   System.out.println("子线程"+Thread.currentThread().getName()+"继续执行");
	   } catch (InterruptedException e) {
		   e.printStackTrace();
	   }
	};
}.start();
 
new Thread(){
	public void run() {
		try {
			System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
			Thread.sleep(5000);
			System.out.println("子线程"+Thread.currentThread().getName()+"达成主线程的条件2");
			latch.countDown();
			System.out.println("子线程"+Thread.currentThread().getName()+"继续执行");
	   } catch (InterruptedException e) {
		   e.printStackTrace();
	   }
	};
}.start();
 
try {
   System.out.println("等待2个子线程达成主线程需要的2个条件...");
   latch.await();
   System.out.println("2个子线程都达成了主线程需要的条件,主线程继续执行");
} catch (InterruptedException e) {
   e.printStackTrace();
}

   执行结果:

子线程Thread-0正在执行
等待2个子线程达成主线程需要的2个条件...
子线程Thread-1正在执行
主线程Thread-0达成主线程的条件1
子线程Thread-0继续执行
子线程Thread-1达成主线程的条件2
2个子线程都达成了主线程需要的条件,主线程继续执行
子线程Thread-1继续执行

   由上面的示例可以看到,CountDownLatch初始化为2,所以需要执行两次countDown()才会返回,否则就一直等到,示例中就是主线程等待2两个子线程,而两个子线程之间不存在相互影响或等待,子线程Thread-0执行了3秒钟就达成了条件1,然后继续做自己的事情,子线程Thread-1却花了5秒钟才达成条件2,然后主线程立即继续往下执行。两个子线程之间不存在相互等待或影响。

 

源码分析

首先,看看CountDownLatch的类结构


从上图可以看出,CountDownLatch非常简单,没有实现和继承任何接口和父类,直接将操作都代理到继承了AQS的静态内部类Sync上。CountDownLatch对外提供的最主要的方法就await/await(timeout)以及countDown():

public void await() throws InterruptedException {
	sync.acquireSharedInterruptibly(1);
}
//直到超时,被中断,或者成功获取到同步资源才返回
//如果在超时到达之前成功获取资源返回true,否则返回false
//该返回值的含义其实也表示是否在超时到达之前,其他线程全部都达到指定的位置(即都执行了countDown()方法将计数器减到0)
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();
}

   从源码可以发现,CountDownLatch确实利用的AQS的共享锁的逻辑,await必须要获取到一个同步资源才能返回,而countDown则是释放一个同步资源,接着看Sync的逻辑:

private static final class Sync extends AbstractQueuedSynchronizer {
	private static final long serialVersionUID = 4982264981922014374L;

	Sync(int count) { //初始化方法,即初始化CountDownLatch时传入的计数器
		setState(count);
	}

	int getCount() { //获取当前剩余的同步资源个数
		return getState();
	}
        //await方法最终的调用次方法尝试获取同步资源
	protected int tryAcquireShared(int acquires) {
		return (getState() == 0) ? 1 : -1; //只有当剩余同步资源为0时才表示成功。
	}
        //countDown方法最终的调用方法-释放同步资源
	protected boolean tryReleaseShared(int releases) {
		// Decrement count; signal when transition to zero
		for (;;) {
			int c = getState();
			if (c == 0) //已经清0,直接返回false
				return false;
			int nextc = c-1;//直接做减1操作,如果成功,并且减到0才返回true,这样才会唤醒调用await阻塞的线程
			if (compareAndSetState(c, nextc))
				return nextc == 0;
		}
	}
}

   由以上的源码可以很清楚CountDownLatch的实现,其原理大致是,使用CountDownLatch的构造方法传入一个正整形数字的参数之后,由AQS维护这相同数量的同步资源个数,调用await的线程除非在线程被中断,等待超时(如果有超时时间的话),或者该同步资源个数为0的时候才会返回,否则一直等待。而当有线程执行了countDown方法之后,如果当前同步资源个数不为0就减1,每执行一次countDown就减一次1,直到减到0才会唤醒等待的线程。减到0之后,如果继续执行countDown则不会有任何反应。

 

所以,当await()方法因为其他线程执行了countDown将计数器减至0被唤醒之后,再次调用await()方法时,肯定是会立即返回的,不论有没有执行countDown,因为其计数器一旦被清0,将无法被重新还原,这也就是CountDownLatch不能被重用的原因。

内存可见性

由于CountDownLatch其内部机制其实就是对声明在AQS中的volatile修饰的state变量的维护,所以CountDownLatch也自然满足volatile语义带来的happens-before原则,即“对一个volatile变量的写操作先行发生于后面对这个变量的读操作”, 因此可以得出,在其他线程中调用countDown(写volatile变量)之前的操作happens-before另一个线程执行await(读volatile变量)返回的操作。

 

也就是说,在其他线程执行countDown之前对共享变量的修改对执行await的线程在await方法返回之后是立即可见的。而那些执行countDown的多个线程(如果存在多个线程的话)之间却不能得出可见性结论。

  • 大小: 4.5 KB
分享到:
评论

相关推荐

    Java并发包之CountDownLatch用法.docx

    `CountDownLatch`是Java并发包`java.util.concurrent`中的一个重要工具类,用于实现线程间的同步。它基于计数器的概念,初始化时设置一个非负的计数值,然后通过调用`countDown()`方法来递减这个计数器。主线程或...

    利用 CountDownLatch 类实现线程同步

    Java 提供了多种工具来实现这样的同步机制,其中之一便是 `CountDownLatch` 类。`CountDownLatch` 是一个计数器,可以用于协调多个线程间的活动,等待所有线程完成各自的任务后,主线程或其他线程才能继续执行。 ...

    java并发包资源

    12. 闭锁 CountDownLatch 13. 栅栏 CyclicBarrier 14. 交换机 Exchanger 15. 信号量 Semaphore 16. 执行器服务 ExecutorService 17. 线程池执行者 ThreadPoolExecutor 18. 定时执行者服务 ScheduledExecutorService ...

    java5 并发包 (concurrent)思维导图

    Java 5并发包(`java.util.concurrent`,简称`Concurrent`包)是Java平台中用于多线程编程的重要组成部分,它提供了丰富的并发工具类,极大地简化了在多线程环境下的编程工作。这个包的设计目标是提高并发性能,减少...

    java并发编程之同步器代码示例

    同步器的设计是基于模版方法模式实现的,使用者需要继承同步器并重写它的抽象方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模版方法,而这些模版方法将会调用使用者重写的方法。 同步器提供...

    Java中的CountDownLatch与CyclicBarrier:深入理解与应用实践

    在Java的并发编程中,CountDownLatch和CyclicBarrier是两个非常重要的同步工具,它们用于协调多个线程的执行顺序。本文将详细介绍CountDownLatch和CyclicBarrier的工作原理、使用场景以及如何在实际项目中应用它们。...

    Java多线程同步.pdf

    在Java语言中,多线程同步机制的实现可以通过synchronized关键字、ReentrantLock类、 Semaphore类、CountDownLatch类等来实现。 1. 使用synchronized关键字 使用synchronized关键字可以对方法或者代码块进行同步。...

    Java中多线程同步类 CountDownLatch

    Java中的CountDownLatch是一种多线程同步工具类,它在并发编程中扮演着重要角色,尤其在需要等待一组任务完成后再进行下一步操作的场景下。CountDownLatch的命名来源于其功能,"count down"意味着计数器向下递减,...

    Java异步调用转同步方法实例详解

    Java中将异步调用转换为同步调用有多种方法,本文将详细介绍五种方法:使用wait和notify方法、使用条件锁、使用Future、使用CountDownLatch、使用CyclicBarrier。 1. 使用wait和notify方法 wait和notify方法是...

    juc详解juc详解juc详解juc详解juc详解juc详解juc详解

    JUC提供了丰富的并发原语,如线程池、同步器、并发容器等,极大地简化了多线程编程的复杂性,并提升了程序的性能和可维护性。以下是对JUC库的详细解析: 1. **线程池(ExecutorService)**: - `ExecutorService`...

    Java中CountDownLatch用法解析

    在Java并发编程中,`CountDownLatch`是一个非常重要的工具类,它位于`java.util.concurrent`包下,用于协调多个线程间的同步。`CountDownLatch`的主要作用是允许一个或多个线程等待其他线程完成操作。在上述例子中,...

    Java concurrency之CountDownLatch原理和示例_动力节点Java学院整理

    它由Java并发包`java.util.concurrent`提供,主要用于解决一种场景:在主控线程等待多个子线程完成各自任务后再继续执行的情况。下面我们将深入探讨CountDownLatch的原理、使用方法以及与CyclicBarrier的区别。 ...

    Java实现进程同步

    5. **CountDownLatch** 和 **CyclicBarrier**:这两个工具类可以帮助我们实现线程间的同步,例如,CountDownLatch可以让一组线程等待其他线程完成操作,而CyclicBarrier则允许一组线程到达一个屏障(或同步点)后...

    java 并发学习总结

    3. **同步工具类**:Java并发包`java.util.concurrent`中的工具类,如`Semaphore`(信号量)、`CyclicBarrier`(回环栅栏)、`CountDownLatch`(倒计时器)和`FutureTask`(未来任务)等,提供了更灵活的线程同步和...

    多线程countDownLatch方法介绍

    在Java多线程编程中,CountDownLatch是一个非常重要的同步工具类,它可以帮助我们协调多个线程之间的交互。本文将深入探讨CountDownLatch的工作原理、使用场景以及相关源码分析。 CountDownLatch是一个计数器,初始...

    深入学习Java同步机制中的底层实现

    AQS是Java并发库的核心,它提供了一种抽象的同步器框架。作为一个抽象类,AQS维护了一个双端FIFO等待队列,用于存储等待获取同步状态的线程。AQS提供了基础的原子操作,如`compareAndSet`,用于线程之间的状态更新和...

    java并发编程中CountDownLatch和CyclicBarrier的使用借鉴.pdf

    java并发编程中CountDownLatch和CyclicBarrier的使用借鉴 java并发编程中CountDownLatch和CyclicBarrier是两个非常重要的线程控制和调度工具,经常被用于解决多线程程序设计中的线程等待问题。本文将对...

    Java中CountDownLatch进行多线程同步详解及实例代码

    CountDownLatch是Java中的一种多线程同步辅助类,主要用来同步多个任务的执行。它允许一个或多个线程等待,直到一组正在其他线程中执行的操作完成。下面是对CountDownLatch的详细解释和实例代码。 CountDownLatch的...

Global site tag (gtag.js) - Google Analytics