并发编程juc包学习3-并发工具类
CyclicBarriar源码解读 : https://mp.weixin.qq.com/s/zXs7ibySkn4F7PM1VxKm2g
Semaphore源码解读 : https://mp.weixin.qq.com/s/Cf3NpdQENtir80V4QKNpMA
CountDownLatch
就是线程计数器,当每次线程执行完任务后就会进行次数减一.直到将次数减为0,(意思就是线程全部执行完了任务)
/**
* 测试CountDownLatch,计数器
* 作用:一个线程等待其他线程全部完成后,程序才会继续运行,否则一直处于等待状态
*/
public class TestCountDownLatch {
static int count = 0;
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
//创建一个count数量为100的计数器
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i = 1; i <= 100; i++) {
final int index = i;
//每循环一次就创建一个线程,并且放到线程池中
threadPool.execute(new Runnable() {
@Override
public void run() {
synchronized (TestCountDownLatch.class) {
count = count +index;
//每调用一次count数量就减一
countDownLatch.countDown();
System.out.println(count+"=====");
}
}
});
}
//计数锁await()结束(只有当count=0才能继续往下执行)
try {
//其实await()的处理逻辑还是比较简单的:如果该线程不是到达的最后一个线程,则他会一直处于等待状态
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count);
//关闭线程池
threadPool.shutdown();
}
}
CyclicBarriar
等待一组线程,线程之间相互等到,只有达到一定数量才会执行下一步.
与CountDownLatch区别:
- CountDownLatch的下一步的动作实施者是主线程,具有不可重复性;
- 而CyclicBarrier的下一步动作实施者还是“其他线程”本身,具有往复多次实施动作的特点。
- 面试题:CountDownLatch与CyclicBarrier区别?
- CountDownLatch:最大特征是进行一个数据减法的操作等待,所有的统计操作一旦开始之中就必须一直执行countDown()方法,
- 如果等待个数不是0将被一直等待,并且无法重置;
- CyclicBarrier:设置一个等待的临界点,并且可以有多个等待线程出现,只要满足了临界点触发了线程的执行代码后将重新开始进行计数处理操作,也可以直接利用reset()方法执行重置操作。
- CyclicBarrier试用与多线程结果合并的操作,用于多线程计算数据,最后合并计算结果的应用场景。比如我们需要统计多个Excel中的数据,然后等到一个总结果。我们可以通过多线程处理每一个Excel,执行完成后得到相应的结果,最后通过barrierAction来计算这些线程的计算结果,得到所有Excel的总和。
/**
*测试CyclicBarrier,循环屏障
* 作用:等待一组线程达到一定的状态.然后在做某件事情.并且该屏障可重用
*/
public class TestCyclicBarrier {
static ExecutorService executorService = Executors.newFixedThreadPool(10);
static CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
//达到某个屏障点后一起做什么事
System.out.println("人数达到了5人,开始活动");
//这里一般都是最后一个线程来执行结束后的任务
System.out.println(Thread.currentThread().getName());
//是不是可以用来关闭资源
//executorService.shutdown();
}
});
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.execute(()->{
play();
});
}
executorService.shutdown();
}
private static void play() {
System.out.println(Thread.currentThread().getName()+"准备就绪");
try {
//当数量不够时会阻塞,程序不会停止.只有达到5个就会执行上边的方法.
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
//System.out.println(Thread.currentThread().getName()+"开始运行");
}
}
Semaphore
信号量,更像是一个许可证,控制访问的数量
Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目
/**
* 测试Semaphore,信号量(许可证书)
* 作用:控制访问的数量,只有指定的数量的线程可以访问,(颁发指定数量的许可)
*/
public class TestSemaphore {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
//同一时间只有2个线程可以工作,(可以发放两个许可)
//true是指公平设置,即先排队的先得到
Semaphore semaphore = new Semaphore(3,true);
for (int i = 0; i <10; i++) {
final int index = i;
executorService.execute(()->{
try {
//获取一个信号量(使用许可)
//当指定数量为x个时,意思是只有获得了x个许可才能执行,同理归还(release)时也要归还同样个数
semaphore.acquire();
play(index);
//增加使用许可,可以额外增加数量
semaphore.release();
//只申请一次许可,规定时间内申请到就true,没有就false.此时有限流作用,同一时间只有2个可以访问服务器
/*if (semaphore.tryAcquire(6, TimeUnit.SECONDS)) {
play(index);
//归还信号量(归还使用许可)
semaphore.release();
} else {
System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date()) +"线程"+index+"申请服务器失败");
}*/
} catch (Exception e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
public static void play(int i) {
System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date()) +"线程"+i+"进入服务器");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date()) +"线程"+i+"退出服务器");
}
}
Future
未来的结果获取,当改结果获取较为费时,可以使用一个线程去执行,在最后需要使用的时候在获取结果.
主要使用在结果不着急使用并且比较费时的场景.
/**
* 测试Future接口,使用线程池计算100以内的质数
*/
public class TestFuture {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
//对0和1求没有意义
for (int i = 2; i < 100; i++) {
ComputePrimeNum primeNum = new ComputePrimeNum();
primeNum.setNum(i);
Future<Boolean> submit = executorService.submit(primeNum);
try {
if (submit.get()) {
System.out.println(i);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
executorService.shutdown();
}
}
class ComputePrimeNum implements Callable<Boolean> {
private int num;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
@Override
public Boolean call() throws Exception {
boolean isPrime = true;
//计算是否是质数要从2开始,并且除去自身
for (int i = 2; i < num; i++) {
if (num%i==0) {
//说明有除了1和自身的还能整除的数,不是质数
isPrime = false;
//找到一个就可以说明了,直接跳出循环
break;
}
}
return isPrime;
}
}
LockSupper
阻塞原语,也好像是一个Semaphore,但是只有一个使用许可.并且要先放开许可,下一个才能获取使用.
/**
* 测试LockSupper阻塞原语
*/
public class TestLockSupper {
static String msg = null ; // 设置一个字符串
public static void main(String[] args) {
// 获得当前的线程操作类
Thread mainThread = Thread.currentThread();
new Thread(()->{
try {
msg = "www.baidu.com" ;
} finally {
// 解锁关起状态,参数是当前线程
LockSupport.unpark(mainThread);
}
}) .start();
//这里的park和unpark就像是一个Semaphore,但是仅有一个许可证,当不进行unpark,是无法使用park的.
//so,这也是一种线程的等待和唤醒
LockSupport.park(mainThread);
System.out.println("********** 主线程执行完毕,msg="+msg);
}
}
Exchanger
Exchanger类可用于两个线程之间交换信息
/**
* 测试exchanger
* java.util.concurrent包中的Exchanger类可用于两个线程之间交换信息。
* 可简单地将Exchanger对象理解为一个包含两个格子的容器,通过exchanger方法可以向两个格子中填充信息。
* 当两个格子中的均被填充时,该对象会自动将两个格子的信息交换,然后返回给线程,从而实现两个线程的信息交换。
*/
public class TestExchanger {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
//传入时test1对应test1,test2对应test2
Thread test1 = new Thread(new ExchangerTest("test1",exchanger));
test1.setName("test1");
//当只有一个时,线程会阻塞.直到两个线程出现,交换数据
Thread test2 = new Thread(new ExchangerTest("test2",exchanger));
test2.setName("test2");
test1.start();
test2.start();
}
}
class ExchangerTest implements Runnable {
private String name;
//公用同一个交换空间
private Exchanger<String> exchanger;
public ExchangerTest(String name,Exchanger<String> exchanger) {
this.name = name;
this.exchanger = exchanger;
}
@Override
public void run() {
System.out.println("交换之前"+Thread.currentThread().getName()+name);
try {
//等待5秒,凑不够2个线程交换就结束
//传入自己的内容,交换另一个线程的内容,也可以传递null,实现一个读一个写
String after = exchanger.exchange(name,5,TimeUnit.SECONDS);
System.out.println("交换之后"+Thread.currentThread().getName()+after);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
CompletableFuture
异步获取线程的执行结果.是Future的进一步实现
目的:模拟获取商店内的商品的价格.商品需要计算价格,你需要掏钱付款.异步就是老板一边计算商品价格你一边掏钱包.
/**
* 测试使用CompletableFuture
* 1.使用异步方法会提高效率
* 2.使用CompletableFuture.supplyAsync(()->{})可以使get()不阻塞,发生异常直接停止.
*/
public class TestCompletableFuture {
public static void main(String[] args) {
Shop shop = new Shop();
long p = System.currentTimeMillis();
//这是同步方法
//int price = shop.getPrice();
//使用异步方法
CompletableFuture<Integer> priceAsync = shop.getPriceAsync2();
Integer price = null;
try {
//设置等待时间,超过了时间就停止,不会阻塞
price = priceAsync.get(4,TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
//模拟其他的工作,就相当于你在掏钱包
//使用异步的话相当于这里的doSome()和Shop中的getPriceAsync()在同时跑.
doSome();
System.out.println("price"+price);
System.out.println("耗时"+(System.currentTimeMillis()-p));
}
public static void doSome() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Shop {
private int price=100;
//使用异步,自己创建CompletableFuture,发生异常后会get()方法会一直阻塞,直到timeout.
public CompletableFuture<Integer> getPriceAsync1() {
CompletableFuture<Integer> completableFuture = new CompletableFuture<>();
new Thread(()->{
int price = this.computePrice();
completableFuture.complete(price);
}).start();
return completableFuture;
}
//使用异步线程,使用静态工厂创建,当当前线程发生异常后,会直接反馈到get()方法,使线程不会阻塞或是等待时间.
public CompletableFuture<Integer> getPriceAsync2() {
//也能使用lambod表达式
/*CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
return computePrice();
});*/
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
return computePrice();
}
});
return future;
}
//使用同步的方式
public int getPrice() {
//模拟延时1s,商品需要计算价格
delay();
return this.computePrice();
}
public int computePrice() {
//设置错误,使线程抛出异常
//int a = 10 / 0;
return 10 * this.price;
}
public void delay() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
流+CompletableFuture
同上边,此时有多个商店,要比价格.
使用的技术:Steam流,(顺序流和并行流)
/**
* 测试并行流
*/
public class TestStream {
public static void main(String[] args) {
Shops shops = new Shops();
long pre = System.currentTimeMillis();
//使用顺序流
//List<String> price = shops.getPrice();
//使用并行流
//List<String> price = shops.getPrice2();
//使用顺序流,使用异步CompletableFuture
//List<String> price = shops.getPrice3();
//使用顺序流,使用异步,指定线程池,这种方式使用起来更灵活,易于业务要求
List<String> price = shops.getPrice4();
for (String s : price) {
System.out.println(s);
}
System.out.println("-----------------分割线---------------");
System.out.println("用时====="+(System.currentTimeMillis()-pre));
}
}
class Shops {
//有5个商店要比价格
private List<String> shopNames = Arrays.asList("1", "2", "3", "4","5");
//使用顺序流
public List<String> getPrice() {
Stream<String> stream = shopNames.stream();
List<String> collect = stream.map(s -> s + "价格是" + computePrice(s)).collect(toList());
return collect;
}
//使用并行流
public List<String> getPrice2() {
//parallelStream()并行处理,其实底层使用的也是线程池,并且最多只有4个线程并行使用
Stream<String> stream = shopNames.parallelStream();
List<String> collect = stream.map(s -> s + "价格是" + computePrice(s)).collect(toList());
return collect;
}
//使用顺序流,在顺序流中使用异步CompletableFuture
public List<String> getPrice3() {
Stream<String> stream = shopNames.stream();
//使用异步方法,建新线程处理每一,与并行流类似
List<CompletableFuture<String>> futures =
stream.map(
s -> CompletableFuture.supplyAsync(
() -> s + "价格是" + computePrice(s)))
.collect(toList());
//取出结果时使用join(),与get()一样,只是不用抛出异常
List<String> collect = futures.stream().map(c -> c.join()).collect(toList());
return collect;
}
//使用循序流,使用异步,使用指定线程池
public List<String> getPrice4() {
//线程池中线程数量根据业务决定也可以这样写,shopNames.size();一个店铺一个
ExecutorService executorService = Executors.newFixedThreadPool(10);
Stream<String> stream = shopNames.stream();
List<CompletableFuture<String>> futures =
stream.map(
s -> CompletableFuture.supplyAsync(
//在使用SupplyAsync创建CompletableFuture时传入自定义的线程池
() -> s + "价格是" + computePrice(s), executorService))
.collect(toList());
List<String> collect = futures.stream().map(c -> c.join()).collect(toList());
//记得关闭线程池
executorService.shutdown();
return collect;
}
private int computePrice(String name) {
//模拟延时
this.delay();
return Integer.valueOf(name) * 1000;
}
public void delay() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}