java.util.concurrent併發包中提供了一系列的的同步工具類,這些基礎類不論是否能在項目中使用到,瞭解一下使用方法和原理對java程序員來講都是有必要的。博主在看《java併發編程實戰》這本書中提到了其中幾個工具類,本文就對這些類進行簡單的描述。java
4個朋友約好下班一塊兒玩吃雞,分別是M4,AWM,SKS,WIN94。這四個哥們下班時間不同,決定好一個時間一塊兒上號搞。程序員
你們約好到家就開遊戲,必須珍惜生命,爭分奪秒玩遊戲。編程
咱們用柵欄來模仿一下場景: 併發
1 public class CyclicBarrierTest { 2 3 private static CyclicBarrier barrier = new CyclicBarrier(4); 4 5 private static class EatChickenPlayer extends Thread { 6 7 private String name; 8 9 //下班時間 10 private Long offWorkTime; 11 12 public EatChickenPlayer(String name, Long time) { 13 this.name = name; 14 this.offWorkTime = time; 15 } 16 17 @Override 18 public void run() { 19 Timer timer = new Timer(); 20 timer.schedule(new TimerTask() { 21 @Override 22 public void run() { 23 System.out.println(name + ":我上號了"); 24 try { 25 barrier.await(); 26 System.out.println(name + ":開打開打"); 27 } catch (Exception e) { 28 e.printStackTrace(); 29 } 30 } 31 }, offWorkTime); 32 } 33 } 34 35 public static void main(String[] args) { 36 EatChickenPlayer m416 = new EatChickenPlayer("m416", 3000L); 37 EatChickenPlayer AWM = new EatChickenPlayer("AWM", 6000L); 38 EatChickenPlayer SKS = new EatChickenPlayer("SKS", 9000L); 39 EatChickenPlayer win94 = new EatChickenPlayer("win94", 4000L); 40 m416.start(); 41 AWM.start(); 42 SKS.start(); 43 win94.start(); 44 } 45 }
運行一下代碼,感覺一下,dom
柵欄一般阻塞一系列線程,當全部須要的線程都達到柵欄位置,才能繼續執行。因此,柵欄能夠理解爲等待其餘的線程到達對應的位置,在一塊兒執行。ide
在作一些併發測試的時候,有時候須要全部線程都執行到相應的位置,讓它們同時執行。並且柵欄是能夠重複利用的,當柵欄開放後,柵欄會進行重置,而後對後續的線程進行攔截。若是await調用超時,或者await的線程被中斷,柵欄就被認爲是打破了,全部await的線程都會停止而且拋出BrokenBarrierException。若是成功經過柵欄,那麼await將爲每一個線程返回一個惟一的到達索引號,咱們能夠經過索引來選舉一個領導線程。並在下一次循環中,經過領導線程執行一些特殊工做。工具
好不容易,四我的都到到齊了,紛紛上號。要進行遊戲,必須等隊伍裏全部人都點擊準備完成,遊戲才能開始。這種時候能夠時候,咱們可使用閉鎖來實現這種業務場景。測試
1 public class CountDownLatchTest { 2 3 private static CountDownLatch countDownLatch = new CountDownLatch(4); 4 5 public static class EatChicken extends Thread { 6 7 private String name; 8 9 private int time; 10 11 public EatChicken(String name, int time) { 12 this.name = name; 13 this.time = time; 14 } 15 16 @Override 17 public void run() { 18 System.out.println(name + "準備" + time + "秒"); 19 try { 20 SECONDS.sleep(time); 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 System.out.println(name + "衣服換好了"); 25 countDownLatch.countDown(); 26 } 27 } 28 29 public static void main(String[] args) throws InterruptedException { 30 System.out.println("-----等待全部人準備-----"); 31 EatChicken m416 = new EatChicken("m416", 1); 32 EatChicken AWM = new EatChicken("AWM", 2); 33 EatChicken SKS = new EatChicken("SKS", 2); 34 EatChicken win94 = new EatChicken("win94", 3); 35 m416.start(); 36 AWM.start(); 37 SKS.start(); 38 win94.start(); 39 40 countDownLatch.await(); 41 System.out.println("-----全部人準備好了-----"); 42 System.out.println("-----遊戲開始-----"); 43 }
運行程序:ui
閉鎖初看跟柵欄很是類似,它們做爲工具類,都能做爲屏障,等待線程執行到必定的地方。閉鎖跟柵欄的區別,就是閉鎖是一次性的,當閉鎖徹底打開後就不能關閉了。柵欄打開以後,放行等待以後,柵欄就被重置,等待下一次開啓。閉鎖使用countDown()的時候,線程不會阻塞,繼續運行。柵欄沒有相似的countDown()方法,使用await()的時候,還須要等待的線程數-1,直到須要等待的線程數爲0的時候,柵欄打開。this
有的時候,不只這四位老哥去玩,時不時的M24也要來一塊兒玩。這個時間,就只能你們一塊兒搶位置了。沒能擠進隊伍的老哥心裏百感交集,欲說還休。
咱們能夠用信號量來模擬。
1 public class SemaphoreTest { 2 3 private static Semaphore semaphore = new Semaphore(4); 4 5 private static class EatChickenPlayer extends Thread { 6 7 private String name; 8 9 10 public EatChickenPlayer(String name) { 11 this.name = name; 12 } 13 14 @Override 15 public void run() { 16 if (semaphore.tryAcquire()) { 17 System.out.println(name + ":我進入遊戲啦"); 18 } else { 19 System.out.println(name + ":臥槽,隊伍滿了"); 20 } 21 } 22 } 23 24 public static void main(String[] args) throws InterruptedException { 25 EatChickenPlayer m416 = new EatChickenPlayer("m416"); 26 EatChickenPlayer AWM = new EatChickenPlayer("AWM"); 27 EatChickenPlayer SKS = new EatChickenPlayer("SKS"); 28 EatChickenPlayer win94 = new EatChickenPlayer("win94"); 29 EatChickenPlayer M24 = new EatChickenPlayer("M24"); 30 m416.start(); 31 AWM.start(); 32 SKS.start(); 33 win94.start(); 34 M24.start(); 35 }
運行一下代碼:
信號量用來控制同時訪問某特定資源的操做數量。經過acquire()阻塞獲取資格。或者使用tryAcquire()方法獲取,及時返回結果,獲取權限成功則返回ture,獲取失敗返回false。當權限已經使用完畢後,調用release()或者release(int permits) 方法釋放權限。
遊戲終於開始了,落地的時候,發現有四個房子,四位胸懷吃雞的老哥分配好各走一個房子收集裏面的東西。收集出來以後一塊兒分東西。這裏把每一個老哥去房子裏舔裝備做爲一個Task。咱們用FutureTask來模擬一下這個場景:
1 package com.chinaredstar.jc.lock; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Map; 6 import java.util.Random; 7 import java.util.concurrent.*; 8 import java.util.stream.Collectors; 9 10 public class FutureTaskTest { 11 12 /** 13 * 固定的4線程,線程池 14 */ 15 private static ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(4); 16 17 static class Equipment { 18 /** 19 * 這裏簡單模擬一下, 20 * 一、子彈 二、槍械 三、藥包 21 */ 22 private Integer type; 23 24 /** 25 * 數量 26 */ 27 private Integer num; 28 29 public Equipment(Integer type, Integer num) { 30 this.type = type; 31 this.num = num; 32 } 33 34 public Integer getType() { 35 return type; 36 } 37 38 public void setType(Integer type) { 39 this.type = type; 40 } 41 42 public Integer getNum() { 43 return num; 44 } 45 46 public void setNum(Integer num) { 47 this.num = num; 48 } 49 50 51 } 52 53 /** 54 * 舔裝備人物類 55 */ 56 static class CollectTask implements Callable { 57 private String name; 58 59 public CollectTask(String name) { 60 this.name = name; 61 } 62 63 @Override 64 public List<Equipment> call() throws Exception { 65 return generatorEquipment(name); 66 } 67 } 68 69 public static List<Equipment> generatorEquipment(String name) { 70 71 List<Equipment> list = new ArrayList<>(); 72 73 Random r = new Random(); 74 //子彈 75 Equipment bullet = new Equipment(1, r.nextInt(100)); 76 list.add(bullet); 77 System.out.println(name + ":撿到子彈" + bullet.num + "發"); 78 //槍 79 Equipment gun = new Equipment(2, r.nextInt(3)); 80 System.out.println(name + ":撿到槍" + gun.num + " 把"); 81 list.add(gun); 82 //藥包 83 Equipment bandage = new Equipment(3, r.nextInt(10)); 84 System.out.println(name + ":撿到繃帶" + bandage.num + " 個"); 85 list.add(bandage); 86 return list; 87 } 88 89 public static void main(String[] args) throws ExecutionException, InterruptedException { 90 //生成任務 91 CollectTask AWM = new CollectTask("AWM"); 92 CollectTask m416 = new CollectTask("m416"); 93 CollectTask SKS = new CollectTask("SKS"); 94 CollectTask win94 = new CollectTask("win94"); 95 //生成任務交給線程池 96 Future AWM_future = threadPoolExecutor.submit(AWM); 97 Future m416_future = threadPoolExecutor.submit(m416); 98 Future SKS_future = threadPoolExecutor.submit(SKS); 99 Future win94_future = threadPoolExecutor.submit(win94); 100 101 //結果放在一塊兒 102 List<Future> futureList = new ArrayList<>(); 103 futureList.add(AWM_future); 104 futureList.add(m416_future); 105 futureList.add(SKS_future); 106 futureList.add(win94_future); 107 108 //結果統一在一塊兒 109 List<Equipment> taskResultList = new ArrayList<>(); 110 for (Future<List<Equipment>> future : futureList) { 111 taskResultList.addAll(future.get()); 112 } 113 114 //打印出來看看都有些啥 115 Map<Integer, Integer> tolal = taskResultList.stream().collect(Collectors.groupingBy( 116 Equipment::getType, Collectors.summingInt(Equipment::getNum))); 117 tolal.entrySet(); 118 for (Map.Entry<Integer, Integer> entry : tolal.entrySet()) { 119 if (entry.getKey() == 1) { 120 System.out.println("總共有子彈:" + entry.getValue()); 121 } 122 if (entry.getKey() == 2) { 123 System.out.println("總共有槍:" + entry.getValue()); 124 } 125 if (entry.getKey() == 3) { 126 System.out.println("總共有藥包:" + entry.getValue()); 127 } 128 } 129 130 } 131 132 }
運行後的代碼結果: