Java并发编程工具类:CountDownLatch、CyclicBarrier、Semaphore
在jdk5中,java提供了一些非常有用的辅助工具类,包括CountDownLatch和CyclicBarrier(两者都可以实现线程之间的通信)、Semaphore(控制方法被线程访问的数量),他们三者都依赖于AQS实现,都是共享锁。今天我们就来学习一下这四个辅助类的用法。
1、CountDownLatch
CountDownLatch基于AQS实现,它由共享锁实现:只要count值不为0(实际上就是state不为0),所有线程都可以进入执行。
CountDownLatch是一个用来控制并发的工具类,它允许一个或者多个线程等待其他的线程执行到某一操作再继续执行。它是通过一个计数器来实现的,计数器的初始值构造方法指定(需要等待多少个操作的完成)。每当一个线程完成了自己的任务后,调用countDown()方法,计数器的值就会减去1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程(即被awiat()调用的线程)就可以恢复执行任务。
构造器中的计数值(count)实际上就是闭锁需要等待的线程数量。这个值只能被设置一次,而且CountDownLatch没有提供任何机制去重新设置这个计数值。
与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
其他N个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。
下面来看一个例子:
此处是一个txt文本文件,目的是通过IO流读取文本的数字进行求和计算。
分析:可以分三行读取计算,线程1执行第一行求得和值1,线程2执行第二行求得和值2,线程3执行第三行求得和值3,最后待线程1,2,3执行完毕后,通过汇总把和值1,2,3进行相加最后得出结果。
package cn.itcats.thread.countdownlatch;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* 使用IO流读取本地文件内容,分线程计算每一行数的和值,并最后汇总各行和值最后计算总和
* 1、使用原始方案完成,后用CountDownLatch完成
* @author fatah
*/
public class CountDownLatchDemo1 {
private int [] nums ;
public CountDownLatchDemo1(int line) {
nums = new int [line];
}
public void lineSum(String lineValue,int index,CountDownLatch latch) {
String[] value = lineValue.split(",");
int sum = 0;
for(String s :value) {
sum += Integer.parseInt(s);
}
nums[index] = sum; //把计算每一行和值放到数组中指定位置
System.out.println(Thread.currentThread().getName()+ " 第"+(index+1)+"行结果为: "+ nums[index]);
//每执行完一次,CountDownLatch-1
latch.countDown();
}
public void sum() {
System.out.println("汇总线程开始执行...");
int total = 0;
for(int i = 0 ; i <nums.length ; i++) {
total += nums[i];
}
System.out.println("最终的结果为:"+ total);
}
public static List<String> readFile() {
FileReader fr = null;
BufferedReader br = null;
String s = null ;
List<String> list = new ArrayList<String>();
try {
fr = new FileReader(new File("/Users/fatah/Desktop/sum.txt"));
br = new BufferedReader(fr);
//每执行到行尾,遇到\r\n换行则添加一次,读取文本完毕时返回null
while((s = br.readLine()) != null) {
//通过BufferedReader缓冲输入流读取每行文本文件,再添加入List中,此时是3个String字符串在list中
list.add(s);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
br.close();
fr.close();
} catch (IOException e) {
}
}
return list;
}
public static void main(String[] args) {
List<String> contents = readFile();
int lineCount = contents.size();
CountDownLatchDemo1 c = new CountDownLatchDemo1(lineCount);
//需要等待3个线程执行完毕,则构造方法传入的是3
CountDownLatch latch = new CountDownLatch(lineCount);
for(int i = 0 ; i < lineCount ;i++) {
final int j = i;
new Thread(new Runnable() {
public void run() {
c.lineSum(contents.get(j), j,latch);
}
}).start();
}
//主线程阻塞,直到countDown为0
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
c.sum();
}
}
源码分析:
package java.util.concurrent;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class CountDownLatch {
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
我是原文复制的源码,这个英文应该不难看懂。它使用AQS的共享式获取同步状态的方式来完成这个功能。
*/
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;
//CAS操作
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
/**
* Constructs a {@code CountDownLatch} initialized with the given count.
*
* @param count the number of times {@link #countDown} must be invoked
* before threads can pass through {@link #await}
* @throws IllegalArgumentException if {@code count} is negative
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
/**
* Causes the current thread to wait until the latch has counted down to
* zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
*
* <p>If the current count is zero then this method returns immediately.
*
* <p>If the current count is greater than zero then the current
* thread becomes disabled for thread scheduling purposes and lies
* dormant until one of two things happen:
* <ul>
* <li>The count reaches zero due to invocations of the
* {@link #countDown} method; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread.
* </ul>
*
* <p>If the current thread:
* <ul>
* <li>has its interrupted status set on entry to this method; or
* <li>is {@linkplain Thread#interrupt interrupted} while waiting,
* </ul>
* then {@link InterruptedException} is thrown and the current thread's
* interrupted status is cleared.
*
* @throws InterruptedException if the current thread is interrupted
* while waiting
*/
//countDown为0,线程立刻被返回
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* Causes the current thread to wait until the latch has counted down to
* zero, unless the thread is {@linkplain Thread#interrupt interrupted},
* or the specified waiting time elapses.
*
* <p>If the current count is zero then this method returns immediately
* with the value {@code true}.
*
* <p>If the current count is greater than zero then the current
* thread becomes disabled for thread scheduling purposes and lies
* dormant until one of three things happen:
* <ul>
* <li>The count reaches zero due to invocations of the
* {@link #countDown} method; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
* <li>The specified waiting time elapses.
* </ul>
*
* <p>If the count reaches zero then the method returns with the
* value {@code true}.
*
* <p>If the current thread:
* <ul>
* <li>has its interrupted status set on entry to this method; or
* <li>is {@linkplain Thread#interrupt interrupted} while waiting,
* </ul>
* then {@link InterruptedException} is thrown and the current thread's
* interrupted status is cleared.
*
* <p>If the specified waiting time elapses then the value {@code false}
* is returned. If the time is less than or equal to zero, the method
* will not wait at all.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the {@code timeout} argument
* @return {@code true} if the count reached zero and {@code false}
* if the waiting time elapsed before the count reached zero
* @throws InterruptedException if the current thread is interrupted
* while waiting
*/
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
* Decrements the count of the latch, releasing all waiting threads if
* the count reaches zero.
*
* <p>If the current count is greater than zero then it is decremented.
* If the new count is zero then all waiting threads are re-enabled for
* thread scheduling purposes.
*
* <p>If the current count equals zero then nothing happens.
*/
public void countDown() {
sync.releaseShared(1);
}
/**
* Returns the current count.
*
* <p>This method is typically used for debugging and testing purposes.
*
* @return the current count
*/
public long getCount() {
return sync.getCount();
}
}
2、CyclicBarrier(循环屏障)
它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。之所以叫Cyclic(循环)是因为该Barrier在释放等待线程后可以重用,所以叫循环的Barrier。
现实中的案例就是:公司需要召集10个人开会,有些人可能先来,有些人可能后到,先来的人需要等待后来的人,只有等所有人都来齐了,才开始会议。
CyclicBarrier类位于java.util.concurrent包下,CyclicBarrier提供2个构造器:
/*它将在给定数量的参与者(线程)处于等待状态下启动,并在启动barrier时候执行给定的屏障操作,该操作由最后一个进入barrier的线程执行
*/
public CyclicBarrier(int parties, Runnable barrierAction) {
}
//它将在给定数量的参与者(线程)处于等待状态下启动,但它不会在启动barrier时候执行预定义操作,如设置值为10,则当有10个人执行await()方法时候启动
public CyclicBarrier(int parties) {
}
我们来模拟上述的现实案例:
package cn.itcats.thread.cyclicbarrier;
import java.util.Random;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//模拟10个人开会,早到则等待,人齐则开始会议
public class Meetting {
Random random =new Random();
public void meeting(CyclicBarrier barrier) {
//随机等待5秒内的时间,模拟等待
try {
Thread.sleep(random.nextInt(5000));
} catch (InterruptedException e1) {
e1.printStackTrace();
};
System.out.println(Thread.currentThread().getName() + " 我到了会议线程,等待开会(即将进入阻塞状态)....");
// 等待
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 轮流发言(唤醒状态)...");
}
public static void main(String[] args) {
//创建CyclicBarrier对象
CyclicBarrier cyclicBarrier = new CyclicBarrier(10, new Runnable() {
public void run() {
System.out.println("10个人都到齐了,开始会议");
}
});
Meetting meetting = new Meetting();
//创建10个线程执行任务
ExecutorService service = Executors.newFixedThreadPool(10);
for(int i = 0 ; i < 10 ; i++) {
service.execute(new Runnable() {
public void run() {
//相当于到了会场签到后等待的任务
meetting.meeting(cyclicBarrier);
}
});
}
service.shutdown();
}
}
注意:若10个线程中,有一个线程发生中断或抛出异常,则无法达到屏障点,await()也无法被唤醒,CyclicBarrier构造方法中的Runnable()任务也不会被执行。
源码分析:
//await() CyclicBarrier核心方法,等待
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
//可以设置超时时间的await(),即到了一定时间,人未来齐也不等了,直接开会,人若提前来齐则提前开会
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
//核心方法 dowait()
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
/*每一个Cycli实例都会引用ReentrantLock,用于加锁,在dowait期间保证线程安全性
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
private final Condition trip = lock.newCondition();
*/
final ReentrantLock lock = this.lock;
lock.lock();
try {
/*用于重置, Generation是CylicBarrier的静态内部类
private static class Generation {
//是否中断,默认是false
boolean broken = false;
}
*/
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
//线程是否被中断过
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//每执行一个await(),count值都会-1,意味着等待队列少了一个
int index = --count;
//当等待数量为0时,会叫醒所有被await()调用的线程
if (index == 0) { // tripped
boolean ranAction = false;
try {
//Runnable即为CycliBarrier构造方法中的任务
final Runnable command = barrierCommand;
if (command != null)
//有任务则执行
command.run();
ranAction = true;
/*叫醒所有
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
//状态重置为初始化值
count = parties;
// boolean broken = false;
generation = new Generation();
}
*/
//重置
nextGeneration();
return 0;
} finally {
//若执行过程中发生异常,执行breakBarrier();
if (!ranAction)
breakBarrier();
}
}
// 如果10个线程,只执行了5个,明显不满足上面的(index == 0),条件都不成立,则执行以下代码
for (;;) {
try {
//不计时,则等待trip.await();
if (!timed)
trip.await();
//计时
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
//等待过程中被中断 可以通过Thread.currentThread().interrupt()测试
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
/*g.broken可以在线程之间传递,如果有一个线程被中断执行了breakBarrier();
则g.broken = true,后续自行的所有的等待线程都会抛出异常 */
if (g.broken)
throw new BrokenBarrierException();
//未被中断,可能被重置也可能超时,返回index
if (g != generation)
return index;
//超时
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
//释放执行dowait()的锁
} finally {
lock.unlock();
}
}
补充一个常用的API:
获取等待线程的数量
public int getNumberWaiting() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return parties - count;
} finally {
lock.unlock();
}
}
是否被中断
public boolean isBroken() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return generation.broken;
} finally {
lock.unlock();
}
}
3、CountDownLatch和CyclicBarrier的区别
- CountDownLatch和CyclicBarrier都为实现线程之间的通信
- CountDownLatch一般用于某个线程等待若干个线程执行完任务之后,它才执行。而CyclicBarrier一般用于若干线程互相等待至某个状态,然后这些线程再同时执行;另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
4、Semaphore(信号量)
Semaphore可以控同时访问的线程个数,在Java并发编程中,信号量控制的是线程并发的数量。Semaphore信号量控制着一定有限值的许可证。当信号量中没有可用的许可的时候,线程阻塞,直到有可用的许可为止。线程可以通过release()方法释放它持有的信号量的许可。每个release方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量。
由于存在竞争——分为公平模式和非公平模式,根据其构造方法指定,底层依赖AQS,改变的也是State的值,它同时允许多个线程进入,是共享锁。
Semaphore类位于java.util.concurrent包下,它提供了2个构造器:
//创建给定的许可数,permits表示许可数目,即同时可以允许多少线程进行访问(默认非公平模式)
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
//创建给定的许可数和是否公平,公平判断依据:等待时间越久的越先获取许可
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
Semaphore底层基于AQS的共享模式实现,Semaphore由内部类Sync类实现,Sync继承抽象类AbstractQueuedSynchronizer。
获得许可:
//获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。
public void acquire() throws InterruptedException { }
//获取permits个许可
public void acquire(int permits) throws InterruptedException { }
//释放一个许可,在释放许可之前,必须先获获得许可
public void release() { }
//释放permits个许可
public void release(int permits) { }
//尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire() {...};
//尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException {...};
//尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(int permits) {...};
//尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException {...};
释放许可:
public void release() {
sync.releaseShared(1);
}
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
public final boolean releaseShared(int arg) {
//若设置释放许可数量成功
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
//重写了AQS中tryReleaseShare方法
protected final boolean tryReleaseShared(int releases) {
//自旋+AQS volatile变量
for (;;) {
//获取当前许可数量
int current = getState();
//计算回收后的数量
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
//CAS改变许可数量成功,返回true
if (compareAndSetState(current, next))
return true;
}
}
获取剩余许可数量:
public int drainPermits() {
return sync.drainPermits();
}
//Sync内部实现
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
减小许可数量:
protected void reducePermits(int reduction) {
if (reduction < 0) throw new IllegalArgumentException();
sync.reducePermits(reduction);
}
//Sync内部实现
final void reducePermits(int reductions) {
for (;;) {
//当前剩余许可数量
int current = getState();
//减少后的许可数量
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
//CAS操作改变AQS中的state变量,即当前许可证的数量。
if (compareAndSetState(current, next))
return;
}
}
推荐阅读
-
Java并发编程工具类:CountDownLatch、CyclicBarrier、Semaphore
-
Java并发学习笔记(九):Semaphore、CountdownLatch、CyclicBarrier
-
Java并发6:CountDownLatch &CyclicBarrier &Semaphore
-
并发编程——彻底掌握CountDownLatch,CyclicBarrier和Semaphore
-
Java并发编程系列---Java中的并发工具类CountDownLatch、CyclicBarrier、Semaphore、Exchanger
-
Java并发编程:CountDownLatch与CyclicBarrier和Semaphore的实例详解
-
Java并发编程:CountDownLatch与CyclicBarrier和Semaphore的实例详解
-
通俗易懂学习java并发工具类-Semaphore,Exchanger
-
通俗易懂学习java并发工具类-Semaphore,Exchanger
-
并发编程CountDownLatch,CyclicBarrier,Semaphore实现原理分析