在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch,CyclicBarrier和Semaphore,今天我们就来学习一下这三个辅助类的用法。
以下是本文目录大纲:
一.CountDownLatch用法
二.CyclicBarrier用法
三.Semaphore用法
若有不正之处请多多谅解,并欢迎批评指正。
请尊重作者劳动成果,转载请标明原文链接:
http://www.cnblogs.com/dolphin0520/p/3920397.html
一.CountDownLatch用法
CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
CountDownLatch类只提供了一个构造器:
1
|
public CountDownLatch( int count) { }; //参数count为计数值
|
然后下面这3个方法是CountDownLatch类中最重要的方法:
1
2
3
|
public void await() throws InterruptedException { }; //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public boolean await( long timeout, TimeUnit unit) throws InterruptedException { }; //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public void countDown() { }; //将count值减1
|
下面看一个例子大家就清楚CountDownLatch的用法了:
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
|
public class Test {
public static void main(String[] args) {
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()+ "执行完毕" );
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
new Thread(){
public void run() {
try {
System.out.println( "子线程" +Thread.currentThread().getName()+ "正在执行" );
Thread.sleep( 3000 );
System.out.println( "子线程" +Thread.currentThread().getName()+ "执行完毕" );
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
try {
System.out.println( "等待2个子线程执行完毕..." );
latch.await();
System.out.println( "2个子线程已经执行完毕" );
System.out.println( "继续执行主线程" );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} |
执行结果:
二.CyclicBarrier用法
字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。
CyclicBarrier类位于java.util.concurrent包下,CyclicBarrier提供2个构造器:
1
2
3
4
5
|
public CyclicBarrier( int parties, Runnable barrierAction) {
} public CyclicBarrier( int parties) {
} |
参数parties指让多少个线程或者任务等待至barrier状态;参数barrierAction为当这些线程都达到barrier状态时会执行的内容。
然后CyclicBarrier中最重要的方法就是await方法,它有2个重载版本:
1
2
|
public int await() throws InterruptedException, BrokenBarrierException { };
public int await( long timeout, TimeUnit unit) throws InterruptedException,BrokenBarrierException,TimeoutException { };
|
第一个版本比较常用,用来挂起当前线程,直至所有线程都到达barrier状态再同时执行后续任务;
第二个版本是让这些线程等待至一定的时间,如果还有线程没有到达barrier状态就直接让到达barrier的线程执行后续任务。
下面举几个例子就明白了:
假若有若干个线程都要进行写数据操作,并且只有所有线程都完成写数据操作之后,这些线程才能继续做后面的事情,此时就可以利用CyclicBarrier了:
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
|
public class Test {
public static void main(String[] args) {
int N = 4 ;
CyclicBarrier barrier = new CyclicBarrier(N);
for ( int i= 0 ;i<N;i++)
new Writer(barrier).start();
}
static class Writer extends Thread{
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier) {
this .cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println( "线程" +Thread.currentThread().getName()+ "正在写入数据..." );
try {
Thread.sleep( 5000 ); //以睡眠来模拟写入数据操作
System.out.println( "线程" +Thread.currentThread().getName()+ "写入数据完毕,等待其他线程写入完毕" );
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e){
e.printStackTrace();
}
System.out.println( "所有线程写入完毕,继续处理其他任务..." );
}
}
} |
执行结果:
从上面输出结果可以看出,每个写入线程执行完写数据操作之后,就在等待其他线程写入操作完毕。
当所有线程线程写入操作完毕之后,所有线程就继续进行后续的操作了。
如果说想在所有线程写入操作完之后,进行额外的其他操作可以为CyclicBarrier提供Runnable参数:
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 Test {
public static void main(String[] args) {
int N = 4 ;
CyclicBarrier barrier = new CyclicBarrier(N, new Runnable() {
@Override
public void run() {
System.out.println( "当前线程" +Thread.currentThread().getName());
}
});
for ( int i= 0 ;i<N;i++)
new Writer(barrier).start();
}
static class Writer extends Thread{
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier) {
this .cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println( "线程" +Thread.currentThread().getName()+ "正在写入数据..." );
try {
Thread.sleep( 5000 ); //以睡眠来模拟写入数据操作
System.out.println( "线程" +Thread.currentThread().getName()+ "写入数据完毕,等待其他线程写入完毕" );
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e){
e.printStackTrace();
}
System.out.println( "所有线程写入完毕,继续处理其他任务..." );
}
}
} |
运行结果:
从结果可以看出,当四个线程都到达barrier状态后,会从四个线程中选择一个线程去执行Runnable。
下面看一下为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
|
public class Test {
public static void main(String[] args) {
int N = 4 ;
CyclicBarrier barrier = new CyclicBarrier(N);
for ( int i= 0 ;i<N;i++) {
if (i<N- 1 )
new Writer(barrier).start();
else {
try {
Thread.sleep( 5000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
new Writer(barrier).start();
}
}
}
static class Writer extends Thread{
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier) {
this .cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println( "线程" +Thread.currentThread().getName()+ "正在写入数据..." );
try {
Thread.sleep( 5000 ); //以睡眠来模拟写入数据操作
System.out.println( "线程" +Thread.currentThread().getName()+ "写入数据完毕,等待其他线程写入完毕" );
try {
cyclicBarrier.await( 2000 , TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "所有线程写入完毕,继续处理其他任务..." );
}
}
} |
执行结果:
上面的代码在main方法的for循环中,故意让最后一个线程启动延迟,因为在前面三个线程都达到barrier之后,等待了指定的时间发现第四个线程还没有达到barrier,就抛出异常并继续执行后面的任务。
另外CyclicBarrier是可以重用的,看下面这个例子:
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
|
public class Test {
public static void main(String[] args) {
int N = 4 ;
CyclicBarrier barrier = new CyclicBarrier(N);
for ( int i= 0 ;i<N;i++) {
new Writer(barrier).start();
}
try {
Thread.sleep( 25000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( "CyclicBarrier重用" );
for ( int i= 0 ;i<N;i++) {
new Writer(barrier).start();
}
}
static class Writer extends Thread{
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier) {
this .cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println( "线程" +Thread.currentThread().getName()+ "正在写入数据..." );
try {
Thread.sleep( 5000 ); //以睡眠来模拟写入数据操作
System.out.println( "线程" +Thread.currentThread().getName()+ "写入数据完毕,等待其他线程写入完毕" );
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "所有线程写入完毕,继续处理其他任务..." );
}
}
} |
执行结果:
从执行结果可以看出,在初次的4个线程越过barrier状态后,又可以用来进行新一轮的使用。而CountDownLatch无法进行重复使用。
三.Semaphore用法
Semaphore翻译成字面意思为 信号量,Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
Semaphore类位于java.util.concurrent包下,它提供了2个构造器:
1
2
3
4
5
6
|
public Semaphore( int permits) { //参数permits表示许可数目,即同时可以允许多少线程进行访问
sync = new NonfairSync(permits);
} public Semaphore( int permits, boolean fair) { //这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可
sync = (fair)? new FairSync(permits) : new NonfairSync(permits);
} |
下面说一下Semaphore类中比较重要的几个方法,首先是acquire()、release()方法:
1
2
3
4
|
public void acquire() throws InterruptedException { } //获取一个许可
public void acquire( int permits) throws InterruptedException { } //获取permits个许可
public void release() { } //释放一个许可
public void release( int permits) { } //释放permits个许可
|
acquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。
release()用来释放许可。注意,在释放许可之前,必须先获获得许可。
这4个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法:
1
2
3
4
|
public boolean tryAcquire() { }; //尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire( long timeout, TimeUnit unit) throws InterruptedException { }; //尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
public boolean tryAcquire( int permits) { }; //尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire( int permits, long timeout, TimeUnit unit) throws InterruptedException { }; //尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
|
另外还可以通过availablePermits()方法得到可用的许可数目。
下面通过一个例子来看一下Semaphore的具体使用:
假若一个工厂有5台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才能继续使用。那么我们就可以通过Semaphore来实现:
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
|
public class Test {
public static void main(String[] args) {
int N = 8 ; //工人数
Semaphore semaphore = new Semaphore( 5 ); //机器数目
for ( int i= 0 ;i<N;i++)
new Worker(i,semaphore).start();
}
static class Worker extends Thread{
private int num;
private Semaphore semaphore;
public Worker( int num,Semaphore semaphore){
this .num = num;
this .semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println( "工人" + this .num+ "占用一个机器在生产..." );
Thread.sleep( 2000 );
System.out.println( "工人" + this .num+ "释放出机器" );
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} |
执行结果:
下面对上面说的三个辅助类进行一个总结:
1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
2)Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。
参考资料:
《Java编程思想》
http://www.itzhai.com/the-introduction-and-use-of-a-countdownlatch.html
http://leaver.me/archives/3220.html
http://developer.51cto.com/art/201403/432095.htm
http://blog.csdn.net/yanhandle/article/details/9016329
package com.ixhong.base.thread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; public class SemaphoreTest { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); final Semaphore sp = new Semaphore(3); for(int i=0;i<10;i++){ Runnable runnable = new Runnable(){ public void run(){ try { sp.acquire(); } catch (InterruptedException e1) { e1.printStackTrace(); } System.out.println("线程" + Thread.currentThread().getName() + "进入,当前已有" + (3-sp.availablePermits()) + "个并发"); try { Thread.sleep((long)(Math.random()*10000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程" + Thread.currentThread().getName() + "即将离开"); sp.release(); //下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元 System.out.println("线程" + Thread.currentThread().getName() + "已离开,当前已有" + (3-sp.availablePermits()) + "个并发"); } }; service.execute(runnable); } } } 线程pool-1-thread-1进入,当前已有2个并发 线程pool-1-thread-3进入,当前已有3个并发 线程pool-1-thread-2进入,当前已有2个并发 线程pool-1-thread-2即将离开 线程pool-1-thread-2已离开,当前已有2个并发 线程pool-1-thread-4进入,当前已有3个并发 线程pool-1-thread-4即将离开 线程pool-1-thread-4已离开,当前已有2个并发 线程pool-1-thread-5进入,当前已有3个并发 线程pool-1-thread-1即将离开 线程pool-1-thread-1已离开,当前已有2个并发 线程pool-1-thread-6进入,当前已有3个并发 线程pool-1-thread-3即将离开 线程pool-1-thread-3已离开,当前已有2个并发 线程pool-1-thread-7进入,当前已有3个并发 线程pool-1-thread-5即将离开 线程pool-1-thread-5已离开,当前已有3个并发 线程pool-1-thread-8进入,当前已有3个并发 线程pool-1-thread-6即将离开 线程pool-1-thread-6已离开,当前已有2个并发 线程pool-1-thread-9进入,当前已有3个并发 线程pool-1-thread-9即将离开 线程pool-1-thread-9已离开,当前已有2个并发 线程pool-1-thread-10进入,当前已有3个并发 线程pool-1-thread-10即将离开 线程pool-1-thread-10已离开,当前已有2个并发 线程pool-1-thread-7即将离开 线程pool-1-thread-7已离开,当前已有1个并发 线程pool-1-thread-8即将离开 线程pool-1-thread-8已离开,当前已有0个并发
java api中Semaphore(信号量),用于控制有限资源的并发访问。API也非常好理解,不过有几个需要注意的地方:
- Semaphore是纯粹的应用级控制“锁”,使用简单的volitale变量作为信号量信息,通过acquire、release、reduce等显式的可以修改此信号量数字。
- 它并没有维护任何锁,也不是控制reentrant的,它不会维护信号和thread的关系。
- Semaphore的初始值可以为0,甚至可以为负数。对于acquire调用(信号down),它只会比较现在信号值与0的大小关系,如果<=0那么将不能获得授权。
- 对于release(信号up),只是简单的对信号值进行原子增加,经过多次的release,信号值可以超过初始的阀值。
- 对于Semaphore(0/-N)的场景,有特殊的使用,这种信号控制,在可以acquire之前,必须经过约定的足够多的release之后才可以被使用。
参考:http://stackoverflow.com/questions/1221322/how-does-semaphore-work
相关推荐
java 高并发应用场景
Java并发编程:CountDownLatch与CyclicBarrier和Semaphore的实例详解 Java并发编程是Java语言中的一种高级技术,用于处理多线程编程中的同步问题。Java 1.5中引入了几个高效的辅助类,包括CountDownLatch、...
在Java并发编程中,CountDownLatch、CyclicBarrier和Semaphore是三种重要的线程协作工具,它们都基于AbstractQueuedSynchronizer(AQS)框架来实现线程间的同步和协调。AQS是一个内置的锁和同步组件,它为构建高级...
AQS 通过 `tryAcquire()` 和 `tryRelease()` 方法控制线程的获取和释放同步状态,而这些方法是由具体实现类(如 ReentrantLock、Semaphore、CountDownLatch 等)来定义的。 **总结:** CountDownLatch 和 ...
本文将详细介绍 Java 并发工具类的四大类:CountDownLatch、Semaphore、CyclicBarrier 和 Phaser,及其应用场景和使用方法。 CountDownLatch CountDownLatch 是一个同步的辅助类,允许一个或多个线程,等待其他一...
4.1 CountDownLatch 4.2 CyclicBarrier 4.3 Semaphore 4.4 Exchanger 第五章:原子类和无锁编程 5.1 AtomicInteger与AtomicLong 5.2 AtomicReference 第六章:线程池及其应用 6.1 ThreadPoolExecutor 6.2...
CountDownLatch、CyclicBarrier 和 Semaphore 等多线程协作机制都是 Java 并发编程中的重要组成部分。它们可以帮助开发者编写高效、可靠的多线程程序,解决复杂的并发问题。 在实际开发中,我们可以根据具体情况...
CountDownLatch CyclicBarrier Semaphore 什么是自旋锁(CAS,compare and swap)? CAS存在的问题 什么是读写锁? 谈谈并发编程三要素 简述Java内存模型(JMM) volatile关键字知道么,它是怎么实现的?(难点 重要) ...
同步工具类:concurrent包提供了一些同步工具类,如CountDownLatch、CyclicBarrier、Semaphore等,可以帮助开发者实现复杂的线程协作和同步任务。 并发集合类:concurrent包提供了一些并发集合类,如...
java并发,主要用于初学者学习,主要案列,Thread.join,ThreadLocal,Lock接口,LockSupport,Condition接口,ConcurrentHashMap的实现原理与...Fork/Join 框架,CountDownLatch,CyclicBarrier,Semaphore,Exchanger
CountDownLatch、CyclicBarrier和Semaphore是典型的同步工具,用于协调多个线程间的操作顺序。 此外,书中还会详细讨论Java内存模型(JMM)和volatile关键字。Java内存模型规定了线程之间如何共享数据,volatile...
多线程控制的三大安全类:CountDownLatch、CyclicBarrier、Semaphore,这工具包将其封装,可以让初学者更容易学习以及让开发者更容易调用,不需要自己重新编写核心代码。具体事例在源码中。
有以下类的实例: ThreadPool ScheduledThread CyclicBarrier BlockingQueue CountDownLatch FutureTask CompletionService Semaphore
如果需要一部分线程完成即可继续,可以选择CyclicBarrier或Semaphore。 在实际编程中,熟练掌握CountDownLatch可以帮助我们更有效地编写高并发程序,解决复杂的同步问题。通过阅读和理解提供的代码样例(如`...
CountDownLatch/CyclicBarrier/Semaphore CountDownLatch 枚举类的使用 CyclicBarrier Semaphore 阻塞队列 SynchronousQueue Callable接口 阻塞队列的应用——生产者消费者 传统模式 阻塞队列模式 阻塞队列的应用...
AQS相关应用(CountDownLatch、CyclicBarrier、Semaphore等),executor(ThreadPoolExecutor、ScheduledThreadPoolExecutor、FutureTask等),collection(ConcurrentHashMap、CopyOnWriteArrayList等), ...
面试者还应熟悉并发容器(如ConcurrentHashMap和CopyOnWriteArrayList)以及并发工具类(如CountDownLatch、CyclicBarrier和Semaphore)。 最后,中间件是Java面试中的另一个重要领域,它涉及到应用程序之间的通信...
如果需要重复使用,考虑使用CyclicBarrier或Semaphore等其他同步工具。 - 误用可能导致死锁,比如计数器设置过大或者`countDown()`未正确调用,可能会导致等待的线程永久阻塞。 通过对`countdownlatch-example-...
java forkjoin 源码 JDK源码学习: Java 容器 ArrayList LinkedList PriorityQueue ...CountDownLatch CyclicBarrier Semaphore ForkJoin FutureTask BlockingQueue Spring AOP IOC 面向面经复习