在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch,CyclicBarrier、Semaphore和Exchange
CountDownLatch
CountDownLatch是一个同步计数器,初始化的时候 传入需要计数的线程等待数,可以是需要等待执行完成的线程数,或者大于。
作用:用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。是一组线程等待其他的线程完成工作以后在执行,相当于加强版join,其中:
await():阻塞当前线程,等待其他线程执行完成,直到计数器计数值减到0。
countDown():负责计数器的减一。
例如:主线程等待其他六个线程执行完成,再执行主线程。
/**
* CountDownLatch计数器演示,由外部线程控制一组线程的放行
*/
public class CountDownLatchDemo {
private static CountDownLatch countDownLatch = new CountDownLatch(6);
private static class InitThread extends Thread {
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "开始初始化....");
countDownLatch.countDown();
}
}
private static class BusiThread extends Thread {
@Override
public void run() {
try {
System.out.println("线程" + Thread.currentThread().getName() + "准备运行....");
//只有等countDownLatch的计数器为0,也就是其他计数器的线程都执行完了,才能执行
countDownLatch.await();
SleepTools.second(1);
System.out.println("线程" + Thread.currentThread().getName() + "运行完成....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "开始初始化....");
countDownLatch.countDown();
}
}, "main-2").start();
new BusiThread().start();
for (int i = 0; i < 5; i++) {
new InitThread().start();
}
try {
countDownLatch.await();
System.out.println("主线程运行结束.......");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
CyclicBarrier
CyclicBarrier字面意思是栅栏,是多线程中一个重要的类,主要用于线程组内部之间的线程的相互等待问题,初始化的时候传入需要等待的线程数。
作用:让一组线程达到某个屏障被阻塞,一直到组内最后一个线程达到屏障时,屏障开放,所有被阻塞的线程才会继续运行。其中:
CyclicBarrier(int parties):初始化定义需要等待的线程数parties。
CyclicBarrier(int parties, Runnable barrierAction):当屏障开放的时候,线程barrierAction的任务会执行。
CountDownLatch和CyclicBarrier的区别:
1、countdownlatch放行由第三者控制,CyclicBarrier放行由一组线程本身控制
2、countdownlatch放行条件》=线程数,CyclicBarrier放行条件=线程数
3、CountDownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程。
4、CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset()方
法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
例如:主线程等待5个子线程执行完成
/**
* CyclicBarrier栅栏演示,由一组线程本身控制放行
*/
public class CyclicBarrierDemo {
//定义一个栅栏,在栅栏放行的时候,同时可以运行CollectThread这个线程
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new CollectThread());
private static ConcurrentHashMap<Long, String> concurrentHashMap = new ConcurrentHashMap();
private static class CollectThread extends Thread {
@Override
public void run() {
System.out.println(concurrentHashMap.size());
}
}
private static class SubThread extends Thread {
@Override
public void run() {
try {
Random random = new Random();
long id = Thread.currentThread().getId();
String name = Thread.currentThread().getName();
concurrentHashMap.put(id, name);
if (random.nextBoolean()) {
System.out.println("线程" + name + "正在处理中....");
SleepTools.second(2);
}
System.out.println("线程" + name + "处理结束,等待其他线程处理结束....");
//每个子线程都会在这里阻塞,等待所有的子线程都执行到这里,才会一起放行
cyclicBarrier.await();
System.out.println("线程" + name + "处理完毕....");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new SubThread().start();
}
//CyclicBarrier,不会阻塞主线程
System.out.println("主线程.....");
}
}
运行结果:
Semaphore
Semaphore又名信号量,是操作系统中的一个概念,在Java并发编程中,信号量控制的是线程并发的数量。
作用:Semaphore管理一系列许可证。每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量,主要控制同时访问某个特定资源的线程数量,多用在流量控制。
注意:其他Semaphore的底层实现就是基于AQS的共享锁实现的。
如果一个线程要访问共享资源,必须先获得信号量,如果信号量的计数器值大于1,意味
着有共享资源可以访问,则使其计数器值减去1,再访问共享资源。如果计数器值为0,线
程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量。
例如:使用Semaphore模拟数据库连接池,Semaphore信号量是可以控制多个线程访问同一资源。
/**
* 模拟数据库连接实现
*/
public class ConnectionImpl implements Connection {
public static Connection getConnection() {
return new ConnectionImpl();
}
@Override
public Statement createStatement() throws SQLException {
//模拟操作
SleepTools.ms(10);
return null;
}
//剩下的重写的方法就省略了........
}
这个类是模拟数据库连接操作,其中重写了一个方法,休眠10毫秒,用于模拟实际操作耗时。
![](/qrcode.jpg)
/**
* 用Semaphore信号量,模拟数据库连接池,Semaphore信号量是可以控制多个线程访问同一资源
*/
public class SemaphoreDemo {
//线程池
private static LinkedList<Connection> list = new LinkedList<>();
//线程池的大小
private static final int POOL_SIZE = 10;
//useful:可以使用的数据库连接数 userless:不能使用的数据库连接数
private static Semaphore useful, userless;
public SemaphoreDemo() {
useful = new Semaphore(POOL_SIZE);
userless = new Semaphore(0);
}
//初始化线程池
static {
for (int i = 0; i < POOL_SIZE; i++) {
list.add(new ConnectionImpl());
}
}
//获取数据库连接
public static Connection getConnection() {
Connection connection = null;
try {
//可用的信号获取许可证,阻塞方法,只有当前线程获取到许可证才能放行
useful.acquire();
synchronized (list) {
//取出连接池中的第一个
connection = list.removeFirst();
}
//不可用的信号释放许可证,将其返回到信号量
userless.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
return connection;
}
//释放连接
public static void releaseConnection(Connection connection) {
if (connection != null) {
System.out.println("当前有" + useful.getQueueLength() + "个线程等待数据库连接!!"
+ "可用连接数:" + useful.availablePermits());
try {
//不可用的信号获取许可证
userless.acquire();
//放回池
synchronized (list) {
list.addLast(connection);
}
//可用的信号释放许可证
useful.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试:
public class TestSemphore {
private static SemaphoreDemo semaphoreDemo = new SemaphoreDemo();
private static class BusiThread extends Thread {
@Override
public void run() {
Random r = new Random();//让每个线程持有连接的时间不一样
long start = System.currentTimeMillis();
Connection connection = semaphoreDemo.getConnection();
System.out.println("Thread_" + Thread.currentThread().getId()
+ "_获取数据库连接共耗时:" + (System.currentTimeMillis() - start) + "ms.....");
//模拟业务操作,线程持有连接查询数据
SleepTools.ms(100 + r.nextInt(100));
System.out.println("查询数据完成,归还连接!");
semaphoreDemo.releaseConnection(connection);
}
}
public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
new BusiThread().start();
}
}
}
运行结果:
Exchange
Exchange类似于一个交换器,可以在对中对元素进行配对和交换的线程的同步点,用于两个线程间的数据交换。
具体来说,Exchanger类允许在两个线程之间定义同步点。当两个线程都到达同步点时,他们交换数据结构,因此第一个线程的数据结构进入到第二个线程中,第二个线程的数据结构进入到第一个线程中。
例如:交换两个线程各自的数据。
public class ExchangerDemo {
private static Exchanger<Set<String>> exchanger = new Exchanger<>();
private static class ExchangerClassO extends Thread {
private Set<String> set;
public ExchangerClassO(Set<String> set) {
this.set = set;
}
@Override
public void run() {
try {
exchange(set);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class ExchangerClassT extends Thread {
private Set<String> set;
public ExchangerClassT(Set<String> set) {
this.set = set;
}
@Override
public void run() {
try {
exchange(set);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static void exchange(Set<String> set) throws InterruptedException {
//交换数据,阻塞
System.out.println("线程:" + Thread.currentThread().getName() + "交换前得值....");
for (String s : set) {
System.out.println(s);
}
exchanger.exchange(set);
System.out.println("线程:" + Thread.currentThread().getName() + "交换后得值....");
for (String s : set) {
System.out.println(s);
}
}
public static void main(String[] args) {
Set<String> setA = new HashSet<>();
Set<String> setB = new HashSet<>();
setA.add("a1");
setA.add("b1");
setA.add("c1");
setB.add("a2");
setB.add("b2");
setB.add("c2");
new ExchangerClassO(setA).start();
new ExchangerClassT(setB).start();
}
}
运行结果: