CountDownLatch&CyclicBarrier
从字面上理解,CountDown表示减法计数,Latch表示门闩的意思,计数为0的时候就可以打开门闩了。 CyclicBarrier表示循环的障碍物。两个类都含有这一个意思:对应的线程都完成工作之后再进行下一步动作,也就是大家都准备好之后再进行下一步。然而两者最大的区别是,进行下一步动作的动作实施者是不一样的。
这里的“动作实施者”有两种,一种是主线程(即执行main函数),另一种是执行任务的其他线程,后面叫这种线程为“其他线程”,区分于主线程。对于CountDownLatch,当计数为0的时候,下一步的动作实施者是main函数;对于CyclicBarrier,下一步动作实施者是“其他线程”。
CyclicBarrier的特性就在于它的计数器是可以重置的,这也就有了上图那种多屏障的情况,虽然第一次调用await使得计数器等于0屏障1失效,但是后续如果继续调用await,屏障还能继续使用,这也就是计数器重置的好处。而CountDownLatch第一次屏障失效后,就结束了
CountDownLatch用法实例
主要方法是
latch.countDown();减法器
latch.await();唤醒
//CyclicBarrier使用测试,成功案例
@Test
void threadTest7() throws Exception {
//这个是接收Future的线程安全的集合
List<Future<List<User>>> list = Collections.synchronizedList(new ArrayList<>());
//CyclicBarrier 拦截器,只有8个线程任务都到了 cyclicBarrier.await()方法(即减到0)才能放行,不然全部阻塞(里面开可以再开一个子线程,案例看下面)
CountDownLatch latch = new CountDownLatch(8);
for (int i = 0; i < 8; i++) {
//这里是采用线程池的方式启动线程,submit就是启动callable接口,会有返回值future
Future<List<User>> future = executor.submit((() -> {
//因为会涉及到线程安全问题,所以就要用线程安全的集合synchronizedList
List<User> userList = Collections.synchronizedList(new ArrayList<>());
//模拟获取大量数据
for (int j = 0; j < 2000; j++) {
//这里是一个获得数据的方法,读者可以自己定义
User user = asyncTaskService.getUser();
userList.add(user);
}
log.info("当前线程=>" + Thread.currentThread().getName() + "===正在获取数据");
//这里设置阻塞,只有8个线程都完成了上面的循环,来到这里,进行减一,只有减到0,才能往后继续,否则先到的线程就会继续阻塞
latch.countDown();
return userList;
}));
list.add(future);
}
//当上面线程完成任务了,会回收回线程池中,这里就是主线程往下运行了
latch.await();//唤醒主线程
int size1 = list.size();
for (int i = 0; i < size1; i++) {
//得到返回结果的List<User>
Future<List<User>> future = list.get(i);
List<User> userList = null;
//判断任务是否返回成功,这里其实可以去掉这个判断,因为这里不是主线程和子线程并行,latch.await()让子线程都运行完回到线程池了,才唤醒的主线程
if (future.isDone()) {
try {
userList = future.get();
log.info("这是第" + i + "返回结果,总共有" + userList.size() + "个结果");
long currentTimeMillis = System.currentTimeMillis();
//userService.saveBatch(userList);可以将数据录入数据库中
log.info("使用的时间是==>" + (System.currentTimeMillis() - currentTimeMillis));
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
}
CyclicBarrier
主要方法: cyclicBarrier.await();计数器
CyclicBarrier用法案例
//CyclicBarrier使用测试,成功案例
@Test
void threadTest7() throws Exception {
//这个是接收Future的线程安全的集合
List<Future<List<User>>> list = Collections.synchronizedList(new ArrayList<>());
//CyclicBarrier 拦截器,只有8个线程任务都到了 cyclicBarrier.await()方法(即减到0)才能放行,不然全部阻塞(里面开可以再开一个子线程,案例看下面)
CyclicBarrier cyclicBarrier = new CyclicBarrier(8);
for (int i = 0; i < 8; i++) {
//这里是采用线程池的方式启动线程,submit就是启动callable接口,会有返回值future
Future<List<User>> future = executor.submit((() -> {
//因为会涉及到线程安全问题,所以就要用线程安全的集合synchronizedList
List<User> userList = Collections.synchronizedList(new ArrayList<>());
//模拟获取大量数据
for (int j = 0; j < 2000; j++) {
//这里是一个获得数据的方法,读者可以自己定义
User user = asyncTaskService.getUser();
userList.add(user);
}
log.info("当前线程=>" + Thread.currentThread().getName() + "===正在获取数据");
//这里设置阻塞,只有8个线程都完成了上面的循环,来到这里,进行减一,只有减到0,才能往后继续,否则先到的线程就会继续阻塞,但并不会影响主线程的继续运行
cyclicBarrier.await();
return userList;
}));
list.add(future);
}
//当上面线程完成任务了,会回收回线程池中,这里就是主线程往下运行了
int size1 = list.size();
for (int i = 0; i < size1; i++) {
//得到返回结果的List<User>
Future<List<User>> future = list.get(i);
List<User> userList = null;
//这里要自旋等待,等上面的线程任务返回成功才得到结果
//为什么要自旋等待,因为这里是主线程和子线程并行,子线程可能还没有运行完,主线程就到这一步了,所以要自旋,等待子线程运行完,返回任务结果。
while (true) {
if (future.isDone()) {
try {
userList = future.get();
log.info("这是第" + i + "返回结果,总共有" + userList.size() + "个结果");
long currentTimeMillis = System.currentTimeMillis();
//userService.saveBatch(userList);可以将数据录入数据库中
log.info("使用的时间是==>" + (System.currentTimeMillis() - currentTimeMillis));
break;
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
}
}
//这个失败的案例值得记录下来
@Test
void threadTest8() throws Exception {
//这个是接收Future的线程安全的集合
List<Future<List<User>>> list = Collections.synchronizedList(new ArrayList<>());
CyclicBarrier cyclicBarrier = new CyclicBarrier(8, () -> {
log.info("你好啊,这里是CyclicBarrier线程");
int size1 = list.size();
// 这里因为是cyclicBarrier.await();满足减到0的条件的同时,开启一个线程,而任务没有结果没有返回出去,所以future.isDone()是false
for (int i = 0; i < size1; i++) {
Future<List<User>> future = list.get(i);
//future.isDone()会判断future的任务是否返回成功
log.info("当前线程=>" + Thread.currentThread().getName() + "===future.isDone()==>"+future.isDone());
}
});
//开启8个线程进行测试
for (int i = 0; i < 8; i++) {
//这里是采用线程池的方式启动线程,submit就是启动callable接口,会有返回值
Future<List<User>> future = executor.submit((() -> {
List<User> userList = Collections.synchronizedList(new ArrayList<>());
//模拟获取大量数据
for (int j = 0; j < 2000; j++) {
//这里是一个获得数据的方法,读者可以自己定义
User user = asyncTaskService.getUser();
userList.add(user);
}
log.info("当前线程=>" + Thread.currentThread().getName() + "===正在获取数据");
//这里设置阻塞,只有8个线程都完成了上面的循环,来到这里,进行减一,只有减到0,才能往后继续,否则先到的线程就会继续阻塞
cyclicBarrier.await();
return userList;
}));
list.add(future);
}
//主线程阻塞,如果不阻塞,可能子线程还没开出来,主线程就已经结束任务了,程序结束
Thread.sleep(2000);
}
Semaphore
Semaphore也是一个线程同步的辅助类,可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数
这个好比是生活的车道一样,给你规定了多少个车道就一次性只能走这么多车,多余的车就在后面的阻塞等待,等前面有车叉出去了,你就可以进去那个车道上
Semaphore的主要方法摘要:
void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。(增加)
void release():释放一个许可,将其返回给信号量。(减少)
int availablePermits():返回此信号量中当前可用的许可数。
boolean hasQueuedThreads():查询是否有线程正在等待获取。
用法
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
//信号量,只允许 3个线程同时访问
Semaphore semaphore = new Semaphore(3);
for (int i=0;i<10;i++){
final long num = i;
executorService.submit(new Runnable() {
@Override
public void run() {
try {
//获取许可
semaphore.acquire();
//执行
System.out.println("Accessing: " + num);
Thread.sleep(new Random().nextInt(5000)); // 模拟随机执行时长
//释放
semaphore.release();
System.out.println("Release..." + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}