(转)并发编程 – Concurrent 用户指南

2019年11月06日 阅读数:417
这篇文章主要向大家介绍(转)并发编程 – Concurrent 用户指南,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。
原文出处:  高广超

译序html

本指南根据 Jakob Jenkov 最新博客翻译,请随时关注博客更新:http://tutorials.jenkov.com/java-util-concurrent/index.html。java

本指南已作成中英文对照阅读版的 pdf 文档,有兴趣的朋友能够去 Java并发工具包java.util.concurrent用户指南中英文对照阅读版.pdf[带书签] 进行下载。算法

1. java.util.concurrent – Java 并发工具包

Java 5 添加了一个新的包到 Java 平台,java.util.concurrent 包。这个包包含有一系列可以让 Java 的并发编程变得更加简单轻松的类。在这个包被添加之前,你须要本身去动手实现本身的相关工具类。编程

本文我将带你一一认识 java.util.concurrent 包里的这些类,而后你能够尝试着如何在项目中使用它们。本文中我将使用 Java 6 版本,我不肯定这和 Java 5 版本里的是否有一些差别。我不会去解释关于 Java 并发的核心问题 – 其背后的原理,也就是说,若是你对那些东西感兴趣,参考《Java 并发指南》。数组

半成品数据结构

本文很大程度上仍是个 “半成品”,因此当你发现一些被漏掉的类或接口时,请耐心等待。在我空闲的时候会把它们加进来的。并发

2. 阻塞队列 BlockingQueue

java.util.concurrent 包里的 BlockingQueue 接口表示一个线程安放入和提取实例的队列。本小节我将给你演示如何使用这个 BlockingQueue。本节不会讨论如何在 Java 中实现一个你本身的 BlockingQueue。若是你对那个感兴趣,参考《Java 并发指南异步

BlockingQueue 用法

BlockingQueue 一般用于一个线程生产对象,而另一个线程消费这些对象的场景。下图是对这个原理的阐述:ide

一个线程往里边放,另一个线程从里边取的一个 BlockingQueue。工具

一个线程将会持续生产新对象并将其插入到队列之中,直到队列达到它所能容纳的临界点。也就是说,它是有限的。若是该阻塞队列到达了其临界点,负责生产的线程将会在往里边插入新对象时发生阻塞。它会一直处于阻塞之中,直到负责消费的线程从队列中拿走一个对象。负责消费的线程将会一直从该阻塞队列中拿出对象。若是消费线程尝试去从一个空的队列中提取对象的话,这个消费线程将会处于阻塞之中,直到一个生产线程把一个对象丢进队列。

BlockingQueue 的方法

BlockingQueue 具备 4 组不一样的方法用于插入、移除以及对队列中的元素进行检查。若是请求的操做不能获得当即执行的话,每一个方法的表现也不一样。这些方法以下:

四组不一样的行为方式解释:

抛异常:若是试图的操做没法当即执行,抛一个异常。
特定值:若是试图的操做没法当即执行,返回一个特定的值(经常是 true / false)。
阻塞:若是试图的操做没法当即执行,该方法调用将会发生阻塞,直到可以执行。
超时:若是试图的操做没法当即执行,该方法调用将会发生阻塞,直到可以执行,但等待时间不会超过给定值。返回一个特定值以告知该操做是否成功(典型的是 true / false)。

没法向一个 BlockingQueue 中插入 null。若是你试图插入 null,BlockingQueue 将会抛出一个 NullPointerException。

能够访问到 BlockingQueue 中的全部元素,而不只仅是开始和结束的元素。好比说,你将一个对象放入队列之中以等待处理,但你的应用想要将其取消掉。那么你能够调用诸如 remove(o) 方法来将队列之中的特定对象进行移除。可是这么干效率并不高(译者注:基于队列的数据结构,获取除开始或结束位置的其余对象的效率不会过高),所以你尽可能不要用这一类的方法,除非你确实不得不那么作。

BlockingQueue 的实现

BlockingQueue 是个接口,你须要使用它的实现之一来使用 BlockingQueue。java.util.concurrent 具备如下 BlockingQueue 接口的实现(Java 6):

Java 中使用 BlockingQueue 的例子

这里是一个 Java 中使用 BlockingQueue 的示例。本示例使用的是 BlockingQueue 接口的 ArrayBlockingQueue 实现。

首先,BlockingQueueExample 类分别在两个独立的线程中启动了一个 Producer 和 一个 Consumer。

Producer 向一个共享的 BlockingQueue 中注入字符串,而 Consumer 则会从中把它们拿出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BlockingQueueExample { 
 
     public static void main(String[] args) throws Exception { 
 
         BlockingQueue queue = new ArrayBlockingQueue( 1024 ); 
 
         Producer producer = new Producer(queue); 
         Consumer consumer = new Consumer(queue); 
 
         new Thread(producer).start(); 
         new Thread(consumer).start(); 
 
         Thread.sleep( 4000 ); 
    
}

如下是 Producer 类。注意它在每次 put() 调用时是如何休眠一秒钟的。这将致使 Consumer 在等待队列中对象的时候发生阻塞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Producer implements Runnable{ 
 
     protected BlockingQueue queue = null
 
     public Producer(BlockingQueue queue) { 
         this .queue = queue; 
    
 
     public void run() { 
         try
             queue.put( "1" ); 
             Thread.sleep( 1000 ); 
             queue.put( "2" ); 
             Thread.sleep( 1000 ); 
             queue.put( "3" ); 
         } catch (InterruptedException e) { 
             e.printStackTrace(); 
        
    
}

如下是 Consumer 类。它只是把对象从队列中抽取出来,而后将它们打印到 System.out。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Consumer implements Runnable{ 
 
     protected BlockingQueue queue = null
 
     public Consumer(BlockingQueue queue) { 
         this .queue = queue; 
    
 
     public void run() { 
         try
             System.out.println(queue.take()); 
             System.out.println(queue.take()); 
             System.out.println(queue.take()); 
         } catch (InterruptedException e) { 
             e.printStackTrace(); 
        
    
}

3. 数组阻塞队列 ArrayBlockingQueue

ArrayBlockingQueue 类实现了 BlockingQueue 接口。

ArrayBlockingQueue 是一个有界的阻塞队列,其内部实现是将对象放到一个数组里。有界也就意味着,它不可以存储无限多数量的元素。它有一个同一时间可以存储元素数量的上限。你能够在对其初始化的时候设定这个上限,但以后就没法对这个上限进行修改了(译者注:由于它是基于数组实现的,也就具备数组的特性:一旦初始化,大小就没法修改)。

‘ArrayBlockingQueue 内部以 FIFO(先进先出)的顺序对元素进行存储。队列中的头元素在全部元素之中是放入时间最久的那个,而尾元素则是最短的那个。

如下是在使用 ArrayBlockingQueue 的时候对其初始化的一个示例:

1
2
3
4
5
BlockingQueue queue = new ArrayBlockingQueue( 1024 ); 
 
queue.put( "1" ); 
 
Object object = queue.take();

如下是使用了 Java 泛型的一个 BlockingQueue 示例。注意其中是如何对 String 元素放入和提取的:

1
2
3
4
5
BlockingQueue<String> queue = new ArrayBlockingQueue<String>( 1024 ); 
 
queue.put( "1" ); 
 
String string = queue.take();

4. 延迟队列 DelayQueue

DelayQueue 实现了 BlockingQueue 接口。DelayQueue 对元素进行持有直到一个特定的延迟到期。注入其中的元素必须实现 java.util.concurrent.Delayed 接口,该接口定义:

1
2
3
4
5
public interface Delayed extends Comparable<Delayed< { 
 
  public long getDelay(TimeUnit timeUnit); 
 
}

DelayQueue 将会在每一个元素的 getDelay() 方法返回的值的时间段以后才释放掉该元素。若是返回的是 0 或者负值,延迟将被认为过时,该元素将会在 DelayQueue 的下一次 take 被调用的时候被释放掉。传递给 getDelay 方法的 getDelay 实例是一个枚举类型,它代表了将要延迟的时间段。

TimeUnit 枚举将会取如下值:

1
2
3
4
5
6
7
8
DAYS 
HOURS 
MINUTES 
SECONDS 
MILLISECONDS 
MICROSECONDS 
NANOSECONDS 
`

正如你所看到的,Delayed 接口也继承了 java.lang.Comparable 接口,这也就意味着 Delayed 对象之间能够进行对比。这个可能在对 DelayQueue 队列中的元素进行排序时有用,所以它们能够根据过时时间进行有序释放。如下是使用 DelayQueue 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
public class DelayQueueExample { 
 
     public static void main(String[] args) { 
         DelayQueue queue = new DelayQueue(); 
 
         Delayed element1 = new DelayedElement(); 
 
         queue.put(element1); 
 
         Delayed element2 = queue.take(); 
    
}

DelayedElement 是我所建立的一个 DelayedElement 接口的实现类,它不在 Java.util.concurrent 包里。你须要自行建立你本身的 Delayed 接口的实现以使用 DelayQueue 类。

5. 链阻塞队列 LinkedBlockingQueue

LinkedBlockingQueue 类实现了 BlockingQueue 接口。

LinkedBlockingQueue 内部以一个链式结构(连接节点)对其元素进行存储。若是须要的话,这一链式结构能够选择一个上限。若是没有定义上限,将使用 Integer.MAX_VALUE 做为上限。

LinkedBlockingQueue 内部以 FIFO(先进先出)的顺序对元素进行存储。队列中的头元素在全部元素之中是放入时间最久的那个,而尾元素则是最短的那个。

如下是 LinkedBlockingQueue 的初始化和使用示例代码:

1
2
3
4
5
6
BlockingQueue<String> unbounded = new LinkedBlockingQueue<String>(); 
BlockingQueue<String> bounded   = new LinkedBlockingQueue<String>( 1024 ); 
 
bounded.put( "Value" ); 
 
String value = bounded.take();

6. 具备优先级的阻塞队列 PriorityBlockingQueue

PriorityBlockingQueue 类实现了 BlockingQueue 接口。

PriorityBlockingQueue 是一个无界的并发队列。它使用了和类 java.util.PriorityQueue 同样的排序规则。你没法向这个队列中插入 null 值。全部插入到 PriorityBlockingQueue 的元素必须实现 java.lang.Comparable 接口。所以该队列中元素的排序就取决于你本身的 Comparable 实现。注意 PriorityBlockingQueue 对于具备相等优先级(compare() == 0)的元素并不强制任何特定行为。

同时注意,若是你从一个 PriorityBlockingQueue 得到一个 Iterator 的话,该 Iterator 并不能保证它对元素的遍历是以优先级为序的。

如下是使用 PriorityBlockingQueue 的示例:

1
2
3
4
5
6
BlockingQueue queue   = new PriorityBlockingQueue(); 
 
     //String implements java.lang.Comparable 
     queue.put( "Value" ); 
 
     String value = queue.take();

7. 同步队列 SynchronousQueue

SynchronousQueue 类实现了 BlockingQueue 接口。

SynchronousQueue 是一个特殊的队列,它的内部同时只可以容纳单个元素。若是该队列已有一元素的话,试图向队列中插入一个新元素的线程将会阻塞,直到另外一个线程将该元素从队列中抽走。一样,若是该队列为空,试图向队列中抽取一个元素的线程将会阻塞,直到另外一个线程向队列中插入了一条新的元素。

据此,把这个类称做一个队列显然是夸大其词了。它更多像是一个汇合点。

8. 阻塞双端队列 BlockingDeque

java.util.concurrent 包里的 BlockingDeque 接口表示一个线程安放入和提取实例的双端队列。本小节我将给你演示如何使用 BlockingDeque。BlockingDeque 类是一个双端队列,在不可以插入元素时,它将阻塞住试图插入元素的线程;在不可以抽取元素时,它将阻塞住试图抽取的线程。deque(双端队列) 是 “Double Ended Queue” 的缩写。所以,双端队列是一个你能够从任意一端插入或者抽取元素的队列。

BlockingDeque 的使用

在线程既是一个队列的生产者又是这个队列的消费者的时候可使用到 BlockingDeque。若是生产者线程须要在队列的两端均可以插入数据,消费者线程须要在队列的两端均可以移除数据,这个时候也可使用 BlockingDeque。

一个 BlockingDeque – 线程在双端队列的两端均可以插入和提取元素。

一个线程生产元素,并把它们插入到队列的任意一端。若是双端队列已满,插入线程将被阻塞,直到一个移除线程从该队列中移出了一个元素。若是双端队列为空,移除线程将被阻塞,直到一个插入线程向该队列插入了一个新元素。

BlockingDeque 的方法

BlockingDeque 具备 4 组不一样的方法用于插入、移除以及对双端队列中的元素进行检查。若是请求的操做不能获得当即执行的话,每一个方法的表现也不一样。这些方法以下:

四组不一样的行为方式解释:

抛异常:若是试图的操做没法当即执行,抛一个异常。
特定值:若是试图的操做没法当即执行,返回一个特定的值(经常是 true / false)。
阻塞:若是试图的操做没法当即执行,该方法调用将会发生阻塞,直到可以执行。
超时:若是试图的操做没法当即执行,该方法调用将会发生阻塞,直到可以执行,但等待时间不会超过给定值。返回一个特定值以告知该操做是否成功(典型的是 true / false)。

BlockingDeque 继承自 BlockingQueue

BlockingDeque 接口继承自 BlockingQueue 接口。

这就意味着你能够像使用一个 BlockingQueue 那样使用 BlockingDeque。若是你这么干的话,各类插入方法将会把新元素添加到双端队列的尾端,而移除方法将会把双端队列的首端的元素移除。正如 BlockingQueue 接口的插入和移除方法同样。

如下是 BlockingDeque 对 BlockingQueue 接口的方法的具体内部实现:

BlockingDeque 的实现

既然 BlockingDeque 是一个接口,那么你想要使用它的话就得使用它的众多的实现类的其中一个。java.util.concurrent 包提供了如下 BlockingDeque 接口的实现类:

LinkedBlockingDeque

BlockingDeque 代码示例

如下是如何使用 BlockingDeque 方法的一个简短代码示例:

1
2
3
4
5
6
7
BlockingDeque<String> deque = new LinkedBlockingDeque<String>(); 
 
deque.addFirst( "1" ); 
deque.addLast( "2" ); 
 
String two = deque.takeLast(); 
String one = deque.takeFirst();

9. 链阻塞双端队列 LinkedBlockingDeque

LinkedBlockingDeque 类实现了 BlockingDeque 接口。

deque(双端队列) 是 “Double Ended Queue” 的缩写。所以,双端队列是一个你能够从任意一端插入或者抽取元素的队列。(译者注:唐僧啊,受不了。)LinkedBlockingDeque 是一个双端队列,在它为空的时候,一个试图从中抽取数据的线程将会阻塞,不管该线程是试图从哪一端抽取数据。如下是 LinkedBlockingDeque 实例化以及使用的示例:

1
2
3
4
5
6
7
BlockingDeque<String> deque = new LinkedBlockingDeque<String>(); 
 
deque.addFirst( "1" ); 
deque.addLast( "2" ); 
 
String two = deque.takeLast(); 
String one = deque.takeFirst();

10. 并发 Map(映射) ConcurrentMap

java.util.concurrent.ConcurrentMap

java.util.concurrent.ConcurrentMap 接口表示了一个可以对别人的访问(插入和提取)进行并发处理的 java.util.Map。ConcurrentMap 除了从其父接口 java.util.Map 继承来的方法以外还有一些额外的原子性方法。

ConcurrentMap 的实现

既然 ConcurrentMap 是个接口,你想要使用它的话就得使用它的实现类之一。java.util.concurrent 包具有 ConcurrentMap 接口的如下实现类:

  • ConcurrentHashMap

ConcurrentHashMap

ConcurrentHashMap 和 java.util.HashTable 类很类似,但 ConcurrentHashMap 可以提供比 HashTable 更好的并发性能。在你从中读取对象的时候 ConcurrentHashMap 并不会把整个 Map 锁住。

此外,在你向其中写入对象的时候,ConcurrentHashMap 也不会锁住整个 Map。它的内部只是把 Map 中正在被写入的部分进行锁定。

另一个不一样点是,在被遍历的时候,即便是 ConcurrentHashMap 被改动,它也不会抛 ConcurrentModificationException。尽管 Iterator 的设计不是为多个线程的同时使用。更多关于 ConcurrentMap 和 ConcurrentHashMap 的细节请参考官方文档。

ConcurrentMap 例子

如下是如何使用 ConcurrentMap 接口的一个例子。

本示例使用了 ConcurrentHashMap 实现类:

1
2
3
4
5
ConcurrentMap concurrentMap = new ConcurrentHashMap(); 
 
concurrentMap.put( "key" , "value" ); 
 
Object value = concurrentMap.get( "key" );

11. 并发导航映射 ConcurrentNavigableMap

java.util.concurrent.ConcurrentNavigableMap 是一个支持并发访问的 java.util.NavigableMap,它还能让它的子 map 具有并发访问的能力。所谓的 “子 map” 指的是诸如 headMap(),subMap(),tailMap() 之类的方法返回的 map。

NavigableMap 中的方法再也不赘述,本小节咱们来看一下 ConcurrentNavigableMap 添加的方法。

headMap()

headMap(T toKey) 方法返回一个包含了小于给定 toKey 的 key 的子 map。若是你对原始 map 里的元素作了改动,这些改动将影响到子 map 中的元素(译者注:map 集合持有的其实只是对象的引用)。如下示例演示了对 headMap() 方法的使用:

1
2
3
4
5
6
7
ConcurrentNavigableMap map = new ConcurrentSkipListMap(); 
 
map.put( "1" , "one" ); 
map.put( "2" , "two" ); 
map.put( "3" , "three" ); 
 
ConcurrentNavigableMap headMap = map.headMap( "2" );

headMap 将指向一个只含有键 “1″ 的 ConcurrentNavigableMap,由于只有这一个键小于 “2″。关于这个方法及其重载版本具体是怎么工做的细节请参考 Java 文档。

tailMap()

tailMap(T fromKey) 方法返回一个包含了不小于给定 fromKey 的 key 的子 map。

若是你对原始 map 里的元素作了改动,这些改动将影响到子 map 中的元素(译者注:map 集合持有的其实只是对象的引用)。

如下示例演示了对 tailMap() 方法的使用:

1
2
3
4
5
6
7
ConcurrentNavigableMap map = new ConcurrentSkipListMap(); 
 
map.put( "1" , "one" ); 
map.put( "2" , "two" ); 
map.put( "3" , "three" ); 
 
ConcurrentNavigableMap tailMap = map.tailMap( "2" );

tailMap 将拥有键 “2″ 和 “3″,由于它们不小于给定键 “2″。关于这个方法及其重载版本具体是怎么工做的细节请参考 Java 文档。

subMap()

subMap() 方法返回原始 map 中,键介于 from(包含) 和 to (不包含) 之间的子 map。

示例以下:

1
2
3
4
5
6
7
ConcurrentNavigableMap map = new ConcurrentSkipListMap(); 
 
map.put( "1" , "one" ); 
map.put( "2" , "two" ); 
map.put( "3" , "three" ); 
 
ConcurrentNavigableMap subMap = map.subMap( "2" , "3" );

返回的 submap 只包含键 “2″,由于只有它知足不小于 “2″,比 “3″ 小。

更多方法

ConcurrentNavigableMap 接口还有其余一些方法可供使用,
好比:

  • descendingKeySet()
  • descendingMap()
  • navigableKeySet()

关于这些方法更多信息参考官方 Java 文档。

12. 闭锁 CountDownLatch

java.util.concurrent.CountDownLatch 是一个并发构造,它容许一个或多个线程等待一系列指定操做的完成。

CountDownLatch 以一个给定的数量初始化。countDown() 每被调用一次,这一数量就减一。经过调用 await() 方法之一,线程能够阻塞等待这一数量到达零。如下是一个简单示例。

Decrementer 三次调用 countDown() 以后,等待中的 Waiter 才会从 await() 调用中释放出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
CountDownLatch latch = new CountDownLatch( 3 ); 
 
Waiter      waiter      = new Waiter(latch); 
Decrementer decrementer = new Decrementer(latch); 
 
new Thread(waiter)     .start(); 
new Thread(decrementer).start(); 
 
Thread.sleep( 4000 ); 
 
public class Waiter implements Runnable{ 
 
     CountDownLatch latch = null
 
     public Waiter(CountDownLatch latch) { 
         this .latch = latch; 
    
 
     public void run() { 
         try
             latch.await(); 
         } catch (InterruptedException e) { 
             e.printStackTrace(); 
        
 
         System.out.println( "Waiter Released" ); 
    
 
public class Decrementer implements Runnable { 
 
     CountDownLatch latch = null
 
     public Decrementer(CountDownLatch latch) { 
         this .latch = latch; 
    
 
     public void run() { 
 
         try
             Thread.sleep( 1000 ); 
             this .latch.countDown(); 
 
             Thread.sleep( 1000 ); 
             this .latch.countDown(); 
 
             Thread.sleep( 1000 ); 
             this .latch.countDown(); 
         } catch (InterruptedException e) { 
             e.printStackTrace(); 
        
    
}

13. 栅栏 CyclicBarrier

java.util.concurrent.CyclicBarrier 类是一种同步机制,它可以对处理一些算法的线程实现同步。换句话讲,它就是一个全部线程必须等待的一个栅栏,直到全部线程都到达这里,而后全部线程才能够继续作其余事情。

图示以下:

两个线程在栅栏旁等待对方。

经过调用 CyclicBarrier 对象的 await() 方法,两个线程能够实现互相等待。一旦 N 个线程在等待 CyclicBarrier 达成,全部线程将被释放掉去继续运行。

建立一个 CyclicBarrier

在建立一个 CyclicBarrier 的时候你须要定义有多少线程在被释放以前等待栅栏。

建立 CyclicBarrier 示例:

1
CyclicBarrier barrier = new CyclicBarrier( 2 );

等待一个 CyclicBarrier

如下演示了如何让一个线程等待一个 CyclicBarrier:

1
barrier.await();

固然,你也能够为等待线程设定一个超时时间。等待超过了超时时间以后,即使尚未达成 N 个线程等待 CyclicBarrier 的条件,该线程也会被释放出来。如下是定义超时时间示例:

1
barrier.await( 10 , TimeUnit.SECONDS);

知足如下任何条件均可以让等待 CyclicBarrier 的线程释放:

  • 最后一个线程也到达 CyclicBarrier(调用 await())
  • 当前线程被其余线程打断(其余线程调用了这个线程的 interrupt() 方法)
  • 其余等待栅栏的线程被打断
  • 其余等待栅栏的线程因超时而被释放
  • 外部线程调用了栅栏的 CyclicBarrier.reset() 方法

CyclicBarrier 行动

CyclicBarrier 支持一个栅栏行动,栅栏行动是一个 Runnable 实例,一旦最后等待栅栏的线程抵达,该实例将被执行。你能够在 CyclicBarrier 的构造方法中将 Runnable 栅栏行动传给它:

1
2
Runnable barrierAction = ... ;
CyclicBarrier barrier = new CyclicBarrier( 2 , barrierAction);

CyclicBarrier 示例

如下代码演示了如何使用 CyclicBarrier:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Runnable barrier1Action = new Runnable() { 
     public void run() { 
         System.out.println( "BarrierAction 1 executed " ); 
    
}; 
Runnable barrier2Action = new Runnable() { 
     public void run() { 
         System.out.println( "BarrierAction 2 executed " ); 
    
}; 
 
CyclicBarrier barrier1 = new CyclicBarrier( 2 , barrier1Action); 
CyclicBarrier barrier2 = new CyclicBarrier( 2 , barrier2Action); 
 
CyclicBarrierRunnable barrierRunnable1 = 
         new CyclicBarrierRunnable(barrier1, barrier2); 
 
CyclicBarrierRunnable barrierRunnable2 = 
         new CyclicBarrierRunnable(barrier1, barrier2); 
 
new Thread(barrierRunnable1).start(); 
new Thread(barrierRunnable2).start();

CyclicBarrierRunnable 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class CyclicBarrierRunnable implements Runnable{ 
 
     CyclicBarrier barrier1 = null
     CyclicBarrier barrier2 = null
 
     public CyclicBarrierRunnable( 
             CyclicBarrier barrier1, 
             CyclicBarrier barrier2) { 
 
         this .barrier1 = barrier1; 
         this .barrier2 = barrier2; 
    
 
     public void run() { 
         try
             Thread.sleep( 1000 ); 
             System.out.println(Thread.currentThread().getName() + 
                                 " waiting at barrier 1" ); 
             this .barrier1.await(); 
 
             Thread.sleep( 1000 ); 
             System.out.println(Thread.currentThread().getName() + 
                                 " waiting at barrier 2" ); 
             this .barrier2.await(); 
 
             System.out.println(Thread.currentThread().getName() + 
                                 " done!" ); 
 
         } catch (InterruptedException e) { 
             e.printStackTrace(); 
         } catch (BrokenBarrierException e) { 
             e.printStackTrace(); 
        
    
}

以上代码控制台输出以下。注意每一个线程写入控制台的时序可能会跟你实际执行不同。好比有时 Thread-0 先打印,有时 Thread-1 先打印。

1
2