它被用来同步一个或多个任务,强制他们等待由其他任务执行的一组操作完成。
你可以向CountDownLatch对象设置一个初始计数值,任何在这个对象上调用wait的方法都将阻塞,直至这个计数值到达0.其他任务在结束其工作时,可以在该对象上调用countDown来减小这个计数值。CountDownLatch被设计为只触发一次,计数值不能被重载。如果你需要能够重置计数值的版本,则可以使用CyclicBarrier.
调用countDown的任务在产生这个调用时并没有被阻塞,只有对await的调用会被阻塞直至计数值到达0.
CountDownLatch的典型用法是将一个程序分为n个互相独立的可解决任务,并创建者为0的CountDownLatch。当每个任务完成时,都会在这个锁存器上调用countDown。等待问题被解决的任务在这个锁存器上调用await,将他们自己拦住,直至锁存器计数结束。
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class TaskPortion implements Runnable {
private static int counter = 0;
private final int id = counter++;
private static Random random = new Random();
private final CountDownLatch latch;
public TaskPortion(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
try {
doWork();
latch.countDown(); //释放锁,如果计数为零,则释放所有的线程
} catch (InterruptedException e) {
//Acceptable way to exit 可接受的退出方式
}
}
public void doWork() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(random.nextInt(2000));
System.out.println(this+ " completed");
}
public String toString() {
return String.format("%1$-3d ",id);
}
}
class WaitingTask implements Runnable {
private static int counter = 0;
private final int id = counter++;
private final CountDownLatch latch;
public WaitingTask(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
try {
//System.out.println("Latch barrier passed for1 "+this);
latch.await();
System.out.println("Latch barrier passed for "+this);
}catch (InterruptedException e) {
System.out.println(this+" interrupted");
}
}
public String toString() {
return String.format("WaitingTask %1$-3d",id);
}
}
public class CountDownLatchDemo {
static final int SIZE = 100;
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
//所有人必须共用一个 CountDownLatch
CountDownLatch countDownLatch = new CountDownLatch(SIZE);
**加粗样式** for (int i = 0; i < 10; i++) {
executorService.execute(new WaitingTask(countDownLatch));
}
for (int i = 0; i < SIZE; i++) {
executorService.execute(new TaskPortion(countDownLatch));
}
System.out.println("Launched all tasks");
executorService.shutdown();
}
}
TaskPortion 将随机地休眠一段时间,以模拟这部分工作的完成,而WaitingTask表示系统中必须等待地部分,它要等待到问题的初始部分完成为止。所有任务都使用了在main中定义的同一个单一的CountDownLatch.
注意,TaskPorition包含一个静态的Random对象,这意味着多个任务可能会同时调用Random.nextInt。这是否安全呢?
如果存在问题,在这种情况下,可以通过想TaskPortion提供其自己的Random对象来解决。也就是说,通过移除static限定符的方式解决。但是这个问题对于Java标准类库中的方法来说,也大都存在,那些是线程安全,那些不是。
遗憾的是,JDK文档并没有指出这一点,Random.nextInt碰巧是安全的,但是你必须通过使用Web引擎,或者审视Java类库代码,去逐个地揭示这一点,这对于被设计为支持,至少理论上支持并发的程序设计语言来说,并非是一件好事。
CyclicBarrier适用于这样的情况:你希望创建一组任务,它们并行地执行工作,然后在进行下一个步骤之前等待,直至所有任务都完成(看起来有些像join).它使得所有的并行任务都将在侧栏列队,因此可以一致地向前移动。这非常像CountDownLatch,只是CountDownLatch是只触发一次的事件,而CyclicBarrier可以多次重用。
从刚开始接触计算机时开始,我就对仿真着了迷,而并发是使仿真成为可能的一个关键因素。记得我最开始编写的一个程序就是一个防止:一个BASIC编写的(由于文件名的卸载而命名为HOSRAC.BAS的赛马游戏)。下面是那个程序的面向对象的多线程版本,其中使用了CyclicBarrier:
import jdk.nashorn.internal.ir.CallNode;
import java.sql.Time;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
class Horse implements Runnable {
private static int counter = 0;
private final int id = counter++;
private int strides = 0;
private static Random random = new Random(47);
private static CyclicBarrier barrier;
public Horse(CyclicBarrier b) {
barrier = b;
}
public synchronized int getStrides() {
return strides;
}
public void run() {
try {
while (!Thread.interrupted()) {
synchronized (this) {
strides+=random.nextInt(3);
}
barrier.await();
}
}catch (InterruptedException e) {
//合法的退出方式
} catch (BrokenBarrierException e) {
//我们想知道的是这个
throw new RuntimeException(e);
}
}
public String toString() {
return "Horse "+id+" ";
}
public String tracks() {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < getStrides(); i++) {
stringBuilder.append("*");
}
stringBuilder.append(id);
return stringBuilder.toString();
}
}
public class HorseRace {
static final int FINISH_LINE = 75;
private List<Horse> horses = new ArrayList<Horse>();
private ExecutorService executorService = Executors.newCachedThreadPool();
private CyclicBarrier barrier;
public HorseRace(int nHorses, final int pause){
barrier = new CyclicBarrier(nHorses, new Runnable() {
@Override
public void run() {
StringBuilder s = new StringBuilder();
for (int i = 0; i < FINISH_LINE; i++) {
s.append("=");//跑道上的栅栏
}
System.out.println("chengpeng: "+s);
for (Horse hors : horses) {
if (hors.getStrides() >= FINISH_LINE) {
System.out.println(hors+" won!");
executorService.shutdownNow();
return;
}
try {
TimeUnit.MILLISECONDS.sleep(pause);
} catch (InterruptedException e) {
System.out.println("barrier-action sleep interruoted");
}
}
}
});
for (int i = 0; i < nHorses; i++) {
Horse horse = new Horse(barrier);
horses.add(horse);
executorService.execute(horse);
}
}
public static void main(String[] args) {
int nHorses = 7;
int pause = 200;
if (args.length > 0) {
int n = new Integer(args[0]);
nHorses = n>0?n:nHorses;
}
if (args.length >1) {
int p = new Integer(args[1]);
pause = p>-1?p:pause;
}
new HorseRace(nHorses,pause);
}
}
可以向CyclicBarrier提供一个侧栏动作,他是一个Runnable,当计数值到达0时自动执行–这是CyclicBarrier和CountDownLatch之间的另一个区别。这里,侧栏动作时作为匿名内部类创建的,它被提交了CyclicBarrier的构造器。
这是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期的时间最长。如果没有任何延迟到期,那么就不会有任何头元素,并且poll将返回null(正因为这样,你不能将null放置到这种队列中)。
下面是一个示例,其中的Delayed对象自身就是任务,而DelayedTaskConsumer将最紧急的任务(到期时间最长的任务)从队列中取出,然后运行它。注意,这样DelayQueue就成为了优先级队列的一种变体:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
import static java.util.concurrent.TimeUnit.*;
class DelayedTask implements Runnable, Delayed {
private static int counter = 0;
private final int id = counter++;
private final int delta;
private final long trigger;
protected static List<DelayedTask> sequence = new ArrayList<DelayedTask>();
public DelayedTask(int delayInMilliseconds) {
delta = delayInMilliseconds;
trigger = System.nanoTime()+NANOSECONDS.convert(delta,MILLISECONDS);
//System.out.println("chengpeng: "+ this);
sequence.add(this);
}
@Override
public int compareTo(Delayed arg) {
DelayedTask that = (DelayedTask)arg;
if (trigger < that.trigger)
return -1;
if (trigger > that.trigger)
return 1;
return 0;
}
@Override
public void run() {
System.out.println(this+" ");
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(trigger-System.nanoTime(),NANOSECONDS);
}
public String toString() {
return String.format("[%1$-4d]",delta)+" Task "+id;
}
public String summary() {
return "("+id+": "+delta+")";
}
public static class EndSentinel extends DelayedTask {
private ExecutorService executorService;
public EndSentinel(int delay, ExecutorService executorService) {
super(delay);
this.executorService =executorService;
}
public void run(){
for (DelayedTask delayedTask : sequence) {
System.out.println(delayedTask.summary()+" ");
}
System.out.println();
System.out.println(this+" Calling shutdownNow()");
executorService.shutdownNow();
}
}
}
class DelayedTaskConsumer implements Runnable {
private DelayQueue<DelayedTask> q;
public DelayedTaskConsumer(DelayQueue<DelayedTask> q) {
this.q = q;
}
public void run() {
try {
while (!Thread.interrupted()) {
//检索并删除此队列的头部,必要时等待 直到延迟过期的元素在此队列上可用。
q.take().run();
}
}catch (InterruptedException e) {
}
System.out.println("Finished DelayedTaskConsumer");
}
}
public class DelayQueueDemo {
public static void main(String[] args) {
Random random = new Random(47);
ExecutorService executorService = Executors.newCachedThreadPool();
DelayQueue<DelayedTask> delayedTasks = new DelayQueue<DelayedTask>();
for (int i = 0; i < 20; i++) {
delayedTasks.put(new DelayedTask(random.nextInt(5000)));//插入元素
}
//设置停止点
delayedTasks.add(new DelayedTask.EndSentinel(5000,executorService));
executorService.execute(new DelayedTaskConsumer(delayedTasks));
}
}
DelayedTask包含一个称为sequence的List,它保存了任务被创建的顺序,因此我可以看到排序是按照实际实际发生的顺序执行的。
Delayed接口有一个方法名为getDelay,它可以用来告知延迟到期有多长时间,或者延迟在多长时间之前已经到期。这个方法将强制我们去使用tImeUnit类,因为这就是参数类型。这会产生一个非常方便的类,因为你可以很容易地转换单位而无需作任何声明。例如delta的值是以毫秒为单位存储的,但是在Java SE5的方法System.nanoTime产生的时间则是以纳秒为单位的。你可以转换delta的值,方法是声明它的单位以及你希望以什么单位来表示,就像下面这样:NANOSECONDS.convert(delta,MILLISECONDS);
在getDelay中,希望使用的单位是作为unit参数传递进来的,你使用它将当前时间与触发时间之间的差转换为调用者要求的单位,而无需知道这些单位是什么(这是策略设计模式的一个简单示例,在这种模式中,算法的一部分是作为参数传递进来的)。
为了排序,Delayed接口还继承了Comparable接口,因此必须实现compareTo,使其可以产生合理的比较,toString和summary提供了输出格式化,而嵌套的EndSentinel类提供了一种关闭所有事物的途径,具体做法是将其放置为队列的最后一个元素。
注意,因为DelayedTaskConsumer自身是一个任务,所以它有自己的Thread,它可以使用这个线程来运行从队列中获取的所有的任务。由于任务时按照队列优先级的顺序执行的,因此在本例中不需要启动任何单独的线程来运行DelayedTask。
从输出中可以看到,任务创建的顺序对执行顺序没有任何影响,任务是按照所期望的延迟顺序执行的。
这是一个很基础的优先级队列,它具有可阻塞的读取操作。下面是一个示例,其中在优先级队列中的对象是按照优先级顺序从队列中出现的任务。PrioritizedTask被赋予了一个优先级数字,以此来提供这种顺序:
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;
class PrioritizedTask implements Runnable,Comparable<PrioritizedTask> {
private Random random = new Random(47);
private static int counter = 0;
private final int id = counter++;
private final int priority;
protected static List<PrioritizedTask> sequence = new ArrayList<PrioritizedTask>();
public PrioritizedTask(int priority) {
this.priority = priority;
sequence.add(this);
}
@Override
public int compareTo(PrioritizedTask arg) {
return priority < arg.priority ? 1:(priority > arg.priority ? -1: 0);
}
@Override
public void run() {
try {
System.out.println("check");
TimeUnit.MILLISECONDS.sleep(random.nextInt(250));
} catch (InterruptedException e) {
}
System.out.println(this +" chengpeng");
}
public String toString() {
return String.format("[%1$-3d]",priority)+" Task "+id;
}
public String summary() {
return "("+id+":"+priority+")";
}
public static class EndSentinel extends PrioritizedTask {
private ExecutorService executorService;
public EndSentinel(ExecutorService executorService) {
super(-1); //这个程序的最低优先级
this.executorService = executorService;
}
public void run() {
int count = 0;
for (PrioritizedTask prioritizedTask : sequence) {
System.out.println(prioritizedTask.summary());
if (++count % 5 == 0) {
System.out.println();
}
}
System.out.println();
System.out.println(this +" Calling shutdownNow()");
executorService.shutdownNow();
}
}
}
class PrioritizedTaskProducer implements Runnable {
private Random random = new Random(47);
private Queue<Runnable> queue;
private ExecutorService executorService;
public PrioritizedTaskProducer(Queue<Runnable> q, ExecutorService executorService) {
queue =q;
this.executorService =executorService;
}
public void run() {
for (int i = 0; i < 20; i++) {
queue.add(new PrioritizedTask(random.nextInt(10)));
Thread.yield();
}
try {
for (int i = 0; i < 10; i++) {
TimeUnit.MILLISECONDS.sleep(250);
queue.add(new PrioritizedTask(10));
}
for (int i = 0; i < 10; i++) {
queue.add(new PrioritizedTask(i));
}
queue.add(new PrioritizedTask.EndSentinel(executorService));
} catch (InterruptedException e){
}
System.out.println("Finished PrioritizedTaskProducer");
}
}
class PrioritizedTaskConsumer implements Runnable {
private PriorityBlockingQueue<Runnable> q;
public PrioritizedTaskConsumer(PriorityBlockingQueue<Runnable> q) {
this.q = q;
}
public void run() {
try {
while (!Thread.interrupted()) {
System.out.println("checking");
q.take().run();
System.out.println("checking1");
}
} catch (InterruptedException e) {
}
System.out.println("Finished PrioritizedTaskConsumer");
}
}
public class PriorityBlockingQueueDemo {
public static void main(String[] args) {
Random random = new Random(47);
ExecutorService executorService = Executors.newCachedThreadPool();
PriorityBlockingQueue<Runnable> queue = new PriorityBlockingQueue<>();
executorService.execute(new PrioritizedTaskProducer(queue,executorService));
executorService.execute(new PrioritizedTaskConsumer(queue));
}
}
与前一个示例相同,PrioritizedTask对象的创建序列被记录在sequence List中,用于和实际的执行顺序比较,run方法将休眠一小段随机的时间,然后打印对象信息,而EndSentinel提供了和前面相同的功能,要确保它是队列中最后一个对象。
PrioritizedTaskProducer和PrioritizedTaskComsumer通过PrioriryBlockingQueue彼此连接,因为这种队列的阻塞特性提供了所有必须得同步,所以你应该注意到了,这里不需要任何显示的同步–不必考虑当你从这种队列中读取时,其中是否有元素,因为这个队列在没有元素时,将直接阻塞读取者。
正常的锁(来自concurrent.locks或内建的synchronized锁)在任何时刻都只允许一个任务访问一项资源,而计数信号量允许n个任务同时访问这个资源。你还可以将信号量看作是在向外分发使用资源的许可证,尽管实际上没有使用任何许可证对象。
作为一个示例,请考虑对象池的概念,它管理着数量有限的对象,当使用对象时可以签出他们,而在用户使用完毕时,可以将他们签回。这种功能可以被封装到一个泛型类中:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
public class Pool<T> {
private int size;
private List<T> items = new ArrayList<T>();
private volatile boolean[] checkedOut;
private Semaphore available;
public Pool(Class<T> classObject, int size) {
this.size = size;
checkedOut = new boolean[size];
available = new Semaphore(size,true);
for (int i = 0; i < size; i++) {
try {
//假设一个默认构造函数
items.add(classObject.newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public T checkOut() throws InterruptedException {
available.acquire(); // 线程占用一个许可
return getItem();
}
public void checkIn(T x) {
if (releaseItem(x)) {
available.release(); // 释放一个锁
}
}
public synchronized T getItem() throws InterruptedException {
for (int i = 0; i < size; i++) {
if (!checkedOut[i]) {
checkedOut[i] = true;
return items.get(i);
}
}
return null; //信号阻止到达这里
}
public synchronized boolean releaseItem(T item) {
int index = items.indexOf(item);
if (index == -1) {
return false;
}
if (checkedOut[index]) {
checkedOut[index] = false;
return true;
}
return false;
}
}
在这个简化的形式中,构造器使用newInstance来把对象加载到池中。如果你需要一个新的对象,那么可以调用checkOut,并且使用完之后,将其递交给checkIn.
boolean类型的数组checkedOut可以跟踪被签出的对象,并且可以通过getItem和releaseItem方法来管理。而这些都将由Semaphore类型的available来加以确保,因此,在checkOut中,如果没有任何信号量许可证可用(这意味着在池中没有更多的对象了),available将阻塞调用过程。在checkIn中,如果被签入的对象有效,则会向信号量返回一个许可证。
为了创建一个示例,我们可以使用Fat,这是一种创建代价高昂的对象类型,因为它的构造器运行起来很耗时:
public class Fat {
private volatile double d; //防止优化
private static int counter = 0;
private final int id = counter++;
public Fat() {
for (int i = 0; i < 10000; i++) {
d+= (Math.PI + Math.E)/(double)i;
}
}
public void operation() {
System.out.println(this);
}
public String toString() {
return "Fat id: " + id;
}
}
我们在池中管理这些对象,以限制这个构造器所造成的影响,我们可以创建一个任务,它将签出Fat对象,持有一段时间之后再将时间之后再将他们签入,以此来测试Pool这个类:
import org.omg.CORBA.MARSHAL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
class CheckoutTask<T> implements Runnable {
private static int counter = 0;
private final int id = counter++;
private Pool<T> pool;
public CheckoutTask(Pool<T> pool) {
this.pool = pool;
}
public void run() {
try {
T item = pool.checkOut();
System.out.println(this + "checked out " +item);
TimeUnit.MILLISECONDS.sleep(1);
System.out.println(this+"checking in "+ item);
pool.checkIn(item);
} catch (InterruptedException e) {
}
}
public String toString() {
return "CheckoutTask " +id +" ";
}
}
public class SemaphoreDemo {
final static int SIZE = 25;
public static void main(String[] args) throws InterruptedException {
final Pool<Fat> fatPool = new Pool<>(Fat.class, SIZE);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < SIZE; i++) {
executorService.execute(new CheckoutTask<Fat>(fatPool));
}
System.out.println("All CheckoutTasks created");
List<Fat> fats = new ArrayList<>();
for (int i = 0; i < SIZE; i++) {
Fat fat = fatPool.checkOut();
System.out.println(i+": main() thread checked out");
fat.operation();
fats.add(fat);
}
Future<?> submit = executorService.submit(new Runnable() {
@Override
public void run() {
try {
//信号量防止额外的检出
//所以调用被阻塞了
fatPool.checkOut();
} catch (Exception e) {
System.out.println("checkOut() Interrupted");
}
}
});
TimeUnit.SECONDS.sleep(2);
submit.cancel(true); //尝试取消任务
System.out.println("Checking in objects in "+fats);
for (Fat fat : fats) {
fatPool.checkIn(fat);
}
for (Fat fat : fats) {
fatPool.checkIn(fat); //第二次签入忽略
}
executorService.shutdown();
}
}
在main中,创建了一个持有Fat对象的Pool,而一组CheckoutTask则开始操练这个Pool.然后,main线程签出池中的Fat对象,但是并不签入他们。一旦池中所有的对象都签出,Semaphore将不再允许执行任何签出操作。Blocked的run方法因此会被阻塞,2秒钟之后,cancel方法被调用,以此来挣脱Future的束缚。注意,冗余的签入将被Pool忽略。
这个示例依赖于Pool的客户端严格地并原因签入所持有的对象,当其工作时,这是最简单的解决方案,如果你无法总是可以依赖于此。
Exchanger是在两个任务之间交换对象的侧栏,当这些任务进入侧栏时,他们各种拥有一个对象,当他们离开时,他们都拥有之前由对象持有的对象。Exchanger的典型应用场景是:一个任务在创建对象,这些对象的生产代表很高昂,而另一个任务在消费这些对象。通过这种方式,可以有更多的对象在被创建的同时被消费。
为了演绎Exchanger类,我们将创建生产者和消费者任务,他们经由泛型和Generator,可以工作于任何类型的对象,然后我们将他们应用于Fater.ExchangerProducer和ExchangerConsumer使用一个List作为要交换的对象,它们都包含一个用于这个List的Exchanger,当你调用Exchanger.exchange方法时,它将阻塞直至对方任务调用它自己的exchange方法,那时,这两个exchange方法将全部完成,而List则被互换:
import net.mindview.util.BasicGenerator;
import net.mindview.util.Generator;
import java.util.List;
import java.util.concurrent.*;
class ExchangerProducer<T> implements Runnable {
private Generator<T> generator;
private Exchanger<List<T>> exchanger;
private List<T> holder;
ExchangerProducer(Exchanger<List<T>> exchg,Generator<T> generator, List<T> holder) {
this.exchanger = exchg;
this.generator = generator;
this.holder = holder;
}
public void run() {
try {
while (!Thread.interrupted()) {
for (int i = 0; i < ExchangerDemo.size; i++) {
holder.add(generator.next());
}
holder = exchanger.exchange(holder);
}
} catch (InterruptedException e) {
}
}
}
class ExchangerConsumer<T> implements Runnable {
private Exchanger<List<T>> exchanger;
private List<T> holder;
private volatile T value;
ExchangerConsumer(Exchanger<List<T>> exchanger,List<T> holder) {
this.exchanger = exchanger;
this.holder = holder;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
holder = exchanger.exchange(holder);
for (T t : holder) {
value = t;
holder.remove(t);
}
}
} catch (InterruptedException e) {
}
System.out.println("Final value: " + value);
}
}
public class ExchangerDemo {
static int size = 10;
static int delay = 5;
public static void main(String[] args) throws InterruptedException {
if (args.length > 0) {
size = new Integer(args[0]);
}
if (args.length >1) {
delay = new Integer(args[1]);
}
ExecutorService executorService = Executors.newCachedThreadPool();
Exchanger<List<Fat>> listExchanger = new Exchanger<>();
List<Fat> fats = new CopyOnWriteArrayList<>();
List<Fat> fats1 = new CopyOnWriteArrayList<>();
executorService.execute(new ExchangerProducer<Fat>(listExchanger, BasicGenerator.create(Fat.class),fats));
executorService.execute(new ExchangerConsumer<Fat>(listExchanger,fats1));
TimeUnit.SECONDS.sleep(delay);
executorService.shutdownNow();
}
}
在main中,创建了用于两个任务的单一的Exchanger,以及两个用于互换的CopyOnWriterArrayList。这个特定的List变体允许在列表被遍历时调用remover方法,而不会抛出异常。ExchangeProducer将填充这个List,然后将这个满列表交换ExchangerConsumer传递给它的空列表。因为有了Exchanger,填充一个列表和消费另一个列表便可以同时发生了。
(不理解)