文章目录
一、等待多线程完成的CountDownLatch
CountDownLatch 允许一个或多个线程等待其他线程完成操作,类似于 join()
CountDownLatch 的构造函数接受一个int类型参数,表示计数器,调用CountDownLatch的 countDown() 方法会使计数器 减一,CountDownLatch的await()方法会阻塞线程直到计数器为0
举例
用一个所有用户(线程)都模拟加载到 100%之后才能开始游戏(主线程运行)的例子体会CountDownLatch的用法
package lockTest;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Description:
* @Author: Aiguodala
* @CreateDate: 2021/4/22 15:52
*/
public class CountDownLatchTest {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(10);
Random random = new Random();
String[] allPlayers = new String[10];
for (int i = 0; i < 10; i++) {
int k = i;
threadPool.submit(() -> {
for (int j = 0; j <= 100; j++) {
try {
Thread.sleep(random.nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
allPlayers[k] = j + "%";
System.out.print("\r" + Arrays.toString(allPlayers));
}
latch.countDown();
});
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
System.out.println("游戏开始");
threadPool.shutdown();
}
}
二、同步屏障CyclicBarrier
CyclicBarrier 做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到所有线程都到达屏障,所有被屏障拦截的线程才会继续运行,可用于多线程计算数据,最后合并计算结果的场景
举例:
模拟某公司记录了全功全一年的每个人对应的工资,现在需要统计公司每个月需要付出多少薪水,先用多线程处理每个人每个月的薪水,处理完成后进入屏障,再用一个线程合并计算结果
package lockTest;
import java.util.Map;
import java.util.concurrent.*;
/**
* @Description:
* @Author: Aiguodala
* @CreateDate: 2021/4/22 16:56
*/
public class CyclicBarrierTest {
public static void main(String[] args) {
Salary salary = new Salary();
salary.count();
}
}
class Salary implements Runnable{
/**
* 创建4个屏障,处理完后执行当前类的run 方法
*/
private CyclicBarrier c = new CyclicBarrier(4, this);
/**
* 模拟只启动4个线程
*/
private Executor executor = Executors.newFixedThreadPool(4);
/**
* 保存每个人的工资
*/
private ConcurrentHashMap<String, Integer> workerSalaryCount = new ConcurrentHashMap<>();
public void count() {
for (int i = 0; i < 4; i++) {
executor.execute(() -> {
// 模拟计算工资
workerSalaryCount.put(Thread.currentThread().getName(), 1000);
try {
c.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
}
}
@Override
public void run() {
int res = 0;
for (Map.Entry<String, Integer> entry : workerSalaryCount.entrySet()) {
res += entry.getValue();
}
workerSalaryCount.put("res",res);
System.out.println(res);
}
}
CountDownLatch 和 CyclicBarrier 的区别
- CountDownLatch 的计数器只能使用一次,但是CyclicBarrier 的计数器可以通过reset()方法重置
- CyclicBarrier 还提供了更多实用方法,如getNumberWaiting() 可以获得阻塞线程数量,isBroken() 方法可以了解阻塞的线程是否被中断等,因此能处理更复杂的业务场景
三、控制并发线程数的Semaphore
Semaphore(信号量),是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理地使用公共资源,可以用于做流量控制,特别是公用资源有限的应用场景
举例
例如有一个需求,需要读取几万个文件的数据,因为都是IO密集型,所以可以启动相应线程并发读取,但是读入后,如果还需要存储到数据库,数据库连接数只有十个,则必须控制只有十个线程能获取数据库连接。
package lockTest;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* @Description:
* @Author: Aiguodala
* @CreateDate: 2021/4/22 17:16
*/
public class SemaphoreTest {
/**
* 线程数
*/
private static final int THREAD_COUNT = 30;
/**
* 固定线程数的连接池
*/
private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
/**
* 信号量控制10
*/
private static Semaphore s = new Semaphore(10);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
int index = i;
threadPool.execute(() -> {
try {
s.acquire();
System.out.println("保存数据" + index);
Thread.sleep(3000);
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
threadPool.shutdown();
}
}
四、线程间交换数据的Exchanger
Exchanger (交换者),是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换,它会提供一个同步点,在都达到同步点时,两个线程可以交换彼此数据,通过exchange()方法,如果一个线程先执行该方法,那么它会等待另一个线程也执行到该方法然后进行数据的交换
举例
例如两个线程计算数据最后进行校验,或者用于遗传算法里需要选出两个人作为交配对象,这时候会交换两人的数据,用相应逻辑得出两人的交配结果,这里用校验数据举例
package lockTest;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Description:
* @Author: Aiguodala
* @CreateDate: 2021/4/23 13:03
*/
public class ExchangerTest {
private static final Exchanger<String> exchager = new Exchanger<>();
private static ExecutorService threadPool = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
threadPool.execute(() -> {
try {
String A = "计算出的数据";
exchager.exchange(A);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threadPool.execute(() -> {
try {
String B = "计算出的数据";
String A = exchager.exchange("B");
System.out.println("A 和 B 的数据是否一致" + A.equals(B));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
参考 《Java并发编程的艺术》