Java并发辅助类的使用

[TOC]

1.概述

本文主要对Java中的关于并发的类的使用写一遍Demo.

具体涉及到的类有:

  1. CountdownLatch
  2. CyclicBarrier
  3. Semaphore

2.CountdownLatch

CountdownLatch类位于java.util.concurrent包下, 利用它可以实现类似计数器的功能, 比如有一个任务C, 它需要等到任务A和任务B执行完成之后才能执行. 此时就可以利用CountdownLatch进行实现.

2-1.构造方法

1public CountDownLatch(int count) { 2 // 省略... 3}

count一般指线程的数量.

2-2.重要方法

1// 调用await()方法的线程会挂起, 它会等到count=0时才继续执行 2public void await() throws InterruptedException { 3 // 省略... 4} 5 6// 与await()方法类似, 只不过是等待一段时间之后不管count值是多少, 都要继续向下执行 7public boolean await(long timeout, TimeUnit unit) 8 throws InterruptedException { 9 // 省略... 10} 11 12// 将count值减1 13public void countDown() { 14 // 省略... 15}

2-3.使用示例

1import java.util.Random; 2import java.util.concurrent.CountDownLatch; 3 4public class CountdownLatchDemo { 5 6 public static void main(String[] args) { 7 new CountdownLatchDemo().start(); 8 } 9 10 private void start() { 11 12 // 定义线程的数量 13 int num = 5; 14 CountDownLatch latch = new CountDownLatch(num); 15 for (int i = 0; i < num; i++) { 16 new Thread(new RunImpl(latch), "线程"+i).start(); 17 } 18 19 try { 20 latch.await(); 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 System.out.println("所有线程写入完毕,继续处理其他任务..."); 25 26 } 27 28 class RunImpl implements Runnable { 29 30 private CountDownLatch latch; 31 32 public RunImpl(CountDownLatch latch) { 33 this.latch = latch; 34 } 35 36 @Override 37 public void run() { 38 try { 39 System.out.println(Thread.currentThread().getName()+"正在写入数据..."); 40 Thread.sleep(new Random().nextInt(2000)); //以睡眠来模拟写入数据操作 41 42 System.out.println(Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕"); 43 44 // 完成任务, 计数器减1 45 latch.countDown(); 46 } catch (Exception e){ 47 e.printStackTrace(); 48 } 49 50 } 51 } 52 53}

输出结果如下:

1线程0正在写入数据... 2线程1正在写入数据... 3线程2正在写入数据... 4线程3正在写入数据... 5线程4正在写入数据... 6线程1写入数据完毕,等待其他线程写入完毕 7线程4写入数据完毕,等待其他线程写入完毕 8线程3写入数据完毕,等待其他线程写入完毕 9线程2写入数据完毕,等待其他线程写入完毕 10线程0写入数据完毕,等待其他线程写入完毕 11所有线程写入完毕,继续处理其他任务...

初始化时CountDownLatch的count为5, 随后启动了5个线程, 然后调用了latch.await(), 挂起main线程, 等待5个线程执行完毕. 每个线程执行完毕时调用了latch.countDown(); 让count减1, 等到count为0时main线程继续执行.

3.CyclicBarrier

与CountdownLatch类似, 只不过它可以进行重用, 而且还可以支持任务执行完之后随即选择一个线程来执行构造方法中传入的线程任务, 还有一点不同是, 主线程main不会堵塞.

3-1.构造方法

1public CyclicBarrier(int parties) { 2 // 省略... 3} 4 5public CyclicBarrier(int parties, Runnable barrierAction) { 6 // 省略... 7}

parties是指让多少个线程或者任务等待至barrier状态, 参数barrierAction是当所有线程都到达barrier状态时, 用最后一个到达barrier的线程执行barrierAction.

3-2.使用示例

1import java.util.Random; 2import java.util.concurrent.CyclicBarrier; 3 4public class CyclicBarrierDemo { 5 6 public static void main(String[] args) { 7 new CyclicBarrierDemo().start(); 8 } 9 10 private void start() { 11 12 int num = 5; 13 CyclicBarrier barrier = new CyclicBarrier(num, () -> System.out.println("最后调用了线程: " + Thread.currentThread().getName())); 14 15 for (int i = 0; i < num; i++) { 16 new Thread(new RunImpl(barrier), "线程"+i).start(); 17 } 18 19 // 等待上面线程执行完后再次执行 20 // try { 21 // Thread.sleep(10000); 22 // } catch (InterruptedException e) { 23 // e.printStackTrace(); 24 // } 25 // for (int i = 0; i < num; i++) { 26 // new Thread(new RunImpl(barrier), "线程"+i).start(); 27 // } 28 29 System.out.println("所有线程写入完毕,继续处理其他任务..."); 30 31 } 32 33 class RunImpl implements Runnable { 34 35 private CyclicBarrier cyclicBarrier; 36 37 public RunImpl(CyclicBarrier cyclicBarrier) { 38 this.cyclicBarrier = cyclicBarrier; 39 } 40 41 @Override 42 public void run() { 43 try { 44 System.out.println(Thread.currentThread().getName()+"正在写入数据..."); 45 Thread.sleep(new Random().nextInt(2000)); //以睡眠来模拟写入数据操作 46 47 System.out.println(Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕"); 48 cyclicBarrier.await(); 49 50 System.out.println(Thread.currentThread().getName()+"await()完成."); 51 } catch (Exception e){ 52 e.printStackTrace(); 53 } 54 } 55 } 56 57}

输出结果如下:

1线程0正在写入数据... 2线程2正在写入数据... 3所有线程写入完毕,继续处理其他任务... 4线程1正在写入数据... 5线程3正在写入数据... 6线程4正在写入数据... 7线程1写入数据完毕,等待其他线程写入完毕 8线程4写入数据完毕,等待其他线程写入完毕 9线程2写入数据完毕,等待其他线程写入完毕 10线程3写入数据完毕,等待其他线程写入完毕 11线程0写入数据完毕,等待其他线程写入完毕 12最后调用了线程: 线程0 13线程0await()完成. 14线程1await()完成. 15线程2await()完成. 16线程4await()完成. 17线程3await()完成.

发现, main方法并不会等待所有线程执行完毕之后执行.

这里没有使用countDown()进行计数器减1, 然后使用await()方法等待计数器变为0, 而是使用await()方法, 并且在await()方法中进行计数器等于0的判断. 并且, 在所有线程到达barrier时, 用最后一个到达的线程去执行barrierAction.

4.Semaphore

Semaphore可以翻译为信号量, 它可以控制同时并发的线程数量, 通过acquire()方法获取许可, 如果没有就等待, 而release()释放一个许可, 注意: 在释放前必须先获取许可.

4-1.构造方法

1public Semaphore(int permits) { 2 sync = new NonfairSync(permits); 3} 4public Semaphore(int permits, boolean fair) { 5 sync = fair ? new FairSync(permits) : new NonfairSync(permits); 6}

permits是允许同时并发的线程数量, 而fair是控制是否允许等待时间越长的线程优先获取许可(公平与不公平).

4-2.重要方法

1public void acquire() throws InterruptedException { 2 sync.acquireSharedInterruptibly(1); 3} 4public void release() { 5 sync.releaseShared(1); 6}

acquire()方法获取许可, release()方法释放许可.

4-3.使用示例

1import java.util.Random; 2import java.util.concurrent.Semaphore; 3 4public class SemaphoreDemo { 5 6 public static void main(String[] args) { 7 new SemaphoreDemo().start(); 8 } 9 10 private void start() { 11 int num = 8; // 工人数 12 Semaphore semaphore = new Semaphore(5); // 机器数目 13 14 for (int i = 0; i < num; i++) { 15 new Thread(new RunImpl(i, semaphore)).start(); 16 } 17 18 } 19 20 class RunImpl implements Runnable { 21 22 private int num; 23 private Semaphore semaphore; 24 public RunImpl(int num, Semaphore semaphore){ 25 this.num = num; 26 this.semaphore = semaphore; 27 } 28 29 @Override 30 public void run() { 31 try { 32 semaphore.acquire(); 33 System.out.println("工人" + this.num + "占用一个机器在生产..."); 34 Thread.sleep(new Random().nextInt(2000)); // 以睡眠来模拟写入数据操作 35 36 System.out.println("工人" + this.num + "释放出机器"); 37 semaphore.release(); 38 } catch (InterruptedException e) { 39 e.printStackTrace(); 40 } 41 } 42 } 43 44}

输出结果如下:

1工人0占用一个机器在生产... 2工人3占用一个机器在生产... 3工人2占用一个机器在生产... 4工人1占用一个机器在生产... 5工人4占用一个机器在生产... 6工人4释放出机器 7工人5占用一个机器在生产... 8工人1释放出机器 9工人6占用一个机器在生产... 10工人6释放出机器 11工人7占用一个机器在生产... 12工人5释放出机器 13工人0释放出机器 14工人3释放出机器 15工人2释放出机器 16工人7释放出机器

这里控制并发线程数量为5个, 工人多了只能等待其它工人释放机器, 再去公平/不公平竞争去使用机器.

5.总结

  1. CountDownLatch一般用于线程A等待若干个线程执行完成任务之后, 它才执行. 不可以重用.
  2. CyclicBarrier一般用于一组线程相互等待至barrier时, 先用最后一个到达的线程执行barrierAction, 再同时执行await()之后的代码. 可以重用.
  3. Semaphore其实和锁有点类似, 用于控制对某个资源的并发控制.
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

一篇文章带你了解JavaScript日期

日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用