併發程式設計13
併發程式設計13
高效能讀寫鎖StampedLock(jdk內部的)
- ReentrantReadWriteLock 的效能已經很好了但是他底層還是需要進行一系列的cas操作去加鎖;
StampedLock如果是讀鎖上鎖是沒有這種cas操作的效能比ReentrantReadWriteLock 更好 - 也稱為樂觀讀鎖;即讀獲取鎖的時候 是不加鎖 直接返回一個值;然後執行臨界區的時候去驗證這個值是否有被人修改(寫操作加鎖)
- 如果沒有被人修改則直接執行臨界區的程式碼;如果被人修改了則需要升級為讀寫鎖
(ReentrantReadWriteLock—>readLock);
基本語法
-
//獲取戳 不存在鎖 long stamp = lock.tryOptimisticRead(); //驗證戳 if(lock.validate(stamp)){ //驗戳成立則執行臨界區的程式碼 //返回 } //如果沒有返回則表示被人修改了 需要升級成為readLock lock.readLock();
兩把讀鎖
-
package BingFaBianCheng.bingFaBianCheng13.shadow.stampedLock; import java.util.concurrent.TimeUnit; public class StampedLockTest { public static void main(String[] args) throws InterruptedException { //例項化資料容器 DataContainer dataContainer = new DataContainer(); //給了一個初始值 不算寫 構造方法賦值 dataContainer.setI(1); //讀取 new Thread(() -> { dataContainer.read(); }, "t1").start(); // 第二個執行緒也去讀取 new Thread(() -> { dataContainer.read(); }, "t2").start(); } }
-
package BingFaBianCheng.bingFaBianCheng13.shadow.stampedLock; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.StampedLock; /** * 一個資料容器 * 不支援重入 * 不支援條件 */ @Slf4j(topic = "enjoy") public class DataContainer { int i; long stampw=0l; public void setI(int i) { this.i = i; } private final StampedLock lock = new StampedLock(); //首先 加 StampedLock @SneakyThrows public int read() { //嘗試一次樂觀讀 long stamp = lock.tryOptimisticRead(); log.debug("StampedLock 讀鎖拿到的戳{}", stamp); //1s之後驗戳 //TimeUnit.SECONDS.sleep(1); //驗戳 if (lock.validate(stamp)) { //執行緒安全問題 // 這裡怎麼都不互斥,因為是無鎖和寫鎖不互斥 log.debug("StampedLock 驗證完畢stamp{}, data.i:{}", stamp, i); TimeUnit.SECONDS.sleep(10); return i; } //一定驗證失敗 log.debug("驗證失敗 被寫執行緒給改變了{}", stampw); try { //鎖的升級 也會改戳 stamp = lock.readLock(); log.debug("升級之後的加鎖成功 {}", stamp); TimeUnit.SECONDS.sleep(1); log.debug("升級讀鎖完畢{}, data.i:{}", stamp, i); return i; } finally { log.debug("升級鎖解鎖 {}", stamp); lock.unlockRead(stamp); } } @SneakyThrows public void write(int i) { //cas 加鎖 stampw = lock.writeLock(); log.debug("寫鎖加鎖成功 {}", stampw); try { TimeUnit.SECONDS.sleep(5); this.i = i; } finally { log.debug("寫鎖解鎖 {},data.i{}", stampw,i); lock.unlockWrite(stampw); } } }
加寫鎖
-
package BingFaBianCheng.bingFaBianCheng13.shadow.stampedLock; import java.util.concurrent.TimeUnit; public class StampedLockTest { public static void main(String[] args) throws InterruptedException { //例項化資料容器 DataContainer dataContainer = new DataContainer(); //給了一個初始值 不算寫 構造方法賦值 dataContainer.setI(1); //讀取 new Thread(() -> { dataContainer.read(); }, "t1").start(); // new Thread(() -> { // dataContainer.read(); // }, "t2").start(); TimeUnit.SECONDS.sleep(3); new Thread(() -> { dataContainer.write(9); }, "t2").start(); } }
那麼StampedLock的效能這麼好能否替代ReentrantReadWriteLock ?
- 1、他不支援重入
- 2、不支援條件佇列
- 3、存在一定的併發問題
samephore
-
來限制對資源訪問的執行緒的上限,而不是資源的上線,除非執行緒和資源是一一對應的;好比洗浴店裡面的手牌,比如你進去一個洗浴店裡,服務生首先會給你一個手牌;如果手牌沒有了你則需要去喝茶等待;等他其他問洗完你才可以去享受服務;手牌相當於你一個許可;你去享受服務的時候先要獲取手牌,服務完成之後需要歸還手牌;
-
比如mysql的連線池就可以,一次只能拿到1個連線;Tomcat就不行,一次可以拿到多個資源
示例
-
package BingFaBianCheng.bingFaBianCheng13.shadow.semaphore; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; /** * 來限制對資源訪問的執行緒上線 */ @Slf4j(topic = "enjoy") public class SemaphoreTest { public static void main(String[] args) { //每次訪問的執行緒上限是3 Semaphore semaphore = new Semaphore(3); for (int i = 0; i < 15; i++) { new Thread(() -> { try { // 獲取許可 semaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } try { log.debug("start..."); // 睡眠1秒鐘 // 每一秒只有3個許可 TimeUnit.SECONDS.sleep(1); log.debug("end..."); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 釋放 +(之前釋放都是 -) semaphore.release(); } }).start(); } } }
獲取許可
- 上面前三個執行緒都會順利拿到鎖,因為tryAcquireShared(arg) >=0
- 第四個執行緒來了,不滿足上面的條件,會執行doAcquireSharedInterruptibly(arg)
- 形成aqs佇列
解釋
-
Node head ->{thread=null prev=null next=t4 ws=-1} Node tail ->{thread=t4 pre=head next=null ws=0} exclusiveOwnerThread:t1、t2、t3 state:0
釋放許可
- 先執行tryReleaseShared方法,取出state,通過cas加1,如果成功,則釋放成功
- 如果上一步釋放鎖成功,則繼續執行doReleaseShared方法,喚醒aqs佇列
總結
- samephore給這把鎖的state賦了一個初值,限定了同時可以獲取鎖的執行緒數目(同時持有並且都沒是方法)
- ReentrantLock初始值是0,加鎖成功會加1,1表示的是鎖被佔有,剛好與samephore相反
CountDownLatch
- 倒數計時鎖;某個執行緒x等待倒數計時為0的時候才執行;所謂的倒數計時其實就是一個int型別的變數,在初始化CountDownLatch的時候會給他一個初始值(程式設計師定的);在多執行緒工作的時候可以通過countDown()方法來對計數器-1;當等於0的時候x則會解阻塞執行
基本語法
-
//初始化物件,給一個初始值 CountDownLatch latch = new CountDownLatch(3); //x執行緒 呼叫await阻塞 等待計數器為0的時候才會解阻塞 latch.await(); //其他執行緒呼叫countDown();對計數器-1 latch.countDown();
通過new thread方法獲取新執行緒
-
package BingFaBianCheng.bingFaBianCheng13.shadow.countDownLatch; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @Slf4j(topic = "enjoy") public class CountDownLatchTest1 { public static void main(String[] args) throws InterruptedException { // ExecutorService executorService = Executors.newFixedThreadPool(3); // executorService //計數器=3 CountDownLatch latch = new CountDownLatch(3); Thread thread = new Thread(() -> { log.debug("t1 thread start"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } //t1 把計數器-1 latch.countDown(); log.debug("t1 thread end;count[{}]", latch.getCount()); }, "t1"); thread.start(); new Thread(() -> { log.debug("t2 thread start"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } latch.countDown(); log.debug("t2 thread end;count[{}]", latch.getCount()); },"t2").start(); new Thread(() -> { log.debug("t3 thread start"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } latch.countDown();//主執行緒可以執行了 log.debug("t3 thread end;count[{}]", latch.getCount()); },"t3").start(); log.debug("main watiing"); latch.await(); log.debug("main wait end..."); } }
countDownLatch和thread.join有什麼區別
- thread.join阻塞的是執行這句的執行緒
- 1.前者可以很方便的配合執行緒池使用,因為實際寫程式碼不可能通過new thread這種方法來建立執行緒
- 2.join是一定等待執行緒執行完軟方法才解阻塞,而countDownLatch在latch.countDown()方法之後就解阻塞了,該方法之後的程式碼有可能和主執行緒同時執行
執行緒池初始化執行緒
-
package BingFaBianCheng.bingFaBianCheng13.shadow.countDownLatch; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; @Slf4j(topic = "enjoy") public class CountDownLatchTest2 { public static void main(String[] args) throws InterruptedException { //執行緒池裡面建立4個執行緒 其中三個是計算的 第四個是彙總的 AtomicInteger i= new AtomicInteger(); ExecutorService executorService = Executors.newFixedThreadPool(4,(e)-> new Thread(e,"t"+i.incrementAndGet())); CountDownLatch latch = new CountDownLatch(3); executorService.submit(()->{ log.debug("t1 thread start"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } latch.countDown(); log.debug("t1 thread end;count[{}]", latch.getCount()); }); executorService.submit(()->{ log.debug("t2 thread start"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } latch.countDown(); log.debug("t2 thread end;count[{}]", latch.getCount()); }); executorService.submit(()->{ log.debug("t3 thread start"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } latch.countDown(); log.debug("t3 thread end;count[{}]", latch.getCount()); }); executorService.submit(()->{ log.debug("t4 watiing"); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("t4 wait end..."); }); executorService.shutdown(); } }
-
執行緒池使用完之後,要注意shutdown()
-
其實前三個方法內容都是一樣的,可以用一個for迴圈來精簡程式碼
-
//執行緒池裡面建立4個執行緒 其中三個是計算的 第四個是彙總的 AtomicInteger i= new AtomicInteger(); // 通過ThreadFactory來執行執行緒中執行緒的名字 ExecutorService executorService = Executors.newFixedThreadPool(4,(e)->{ return new Thread(e,"t"+i.incrementAndGet()); });
模擬四個子執行緒執行不同的工作,主執行緒等所有子執行緒執行完後才繼續往下執行
-
package BingFaBianCheng.bingFaBianCheng13.shadow.countDownLatch; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; @Slf4j(topic = "enjoy") public class CountDownLatchTest3 { public static void main(String[] args) throws InterruptedException { //執行緒池裡面建立3個執行緒 List<String> list = new ArrayList<>(); list.add("Angel"); list.add("baby"); list.add("rose"); list.add("joyce"); AtomicInteger i= new AtomicInteger(); ExecutorService executorService = Executors.newFixedThreadPool(4,(runnable)->{ //技師的名字 return new Thread(runnable,list.get(i.getAndIncrement())); }); //讓你先去沙發上休息 CountDownLatch latch = new CountDownLatch(4); Random random = new Random(); for (int j = 0; j <4 ; j++) {//new 4個執行緒 併發執行 int temp =j; executorService.submit(()->{ //k標識的是準備進度 直到準備到100% 才開始服務 這個時間每個技師不固定 因為是random for (int k = 0; k <100 ; k++) { try { //模擬每一個技師準備的時間 TimeUnit.MILLISECONDS.sleep(random.nextInt(200)); } catch (InterruptedException e) { e.printStackTrace(); } String name = Thread.currentThread().getName(); name=name+"("+k+"%)";//angel(3%) baby(10%) ... list.set(temp,name); System.out.print("\r"+Arrays.toString(list.toArray())); } //某個人準備好了 latch.countDown(); }); } latch.await(); System.out.println("\n 登上人生巔峰..."); executorService.shutdown(); } }
CyclicBarrier
- cyclicBarrier 重複柵欄(CountDownLatch),語法和CountDownLatch差不多,但是可重複
基本語法
-
//初始化一個cyclicBarrier 計數器為2 CyclicBarrier cyclicBarrier = new CyclicBarrier(2); //阻塞 計數器不為0的時候並且會把計數器-1 cyclicBarrier.await();
與countDownlatch區別
-
1.需要cyclicBarrier管理的程式碼塊是可以重複執行的,而countDownLatch想重複執行,必須new多個相同的countDownLatch
-
CyclicBarrier cyclicBarrier = new CyclicBarrier(2,()->{ log.debug("t1 t2 end"); });
2.CyclicBarrier第二個引數的方法會在CyclicBarrier等於0後執行,起到彙總的作用
示例程式碼1
-
package BingFaBianCheng.bingFaBianCheng13.shadow.cyclic; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; @Slf4j(topic = "enjoy") public class CyclicBarrierTest { public static void main(String[] args) { AtomicInteger i= new AtomicInteger(); CyclicBarrier cyclicBarrier = new CyclicBarrier(2,()->{ log.debug("t1 t2 end"); }); ExecutorService service = Executors.newFixedThreadPool(2); for (int j = 0; j <2 ; j++) { service.submit(()->{ log.debug("start"); try { TimeUnit.SECONDS.sleep(1); log.debug("working"); cyclicBarrier.await(); } catch (Exception e) { e.printStackTrace(); } }); service.submit(()->{ log.debug("start"); try { TimeUnit.SECONDS.sleep(3); log.debug("working"); cyclicBarrier.await(); } catch (Exception e) { e.printStackTrace(); } }); } service.shutdown(); } }
相關文章
- 併發程式設計程式設計
- 【Java併發程式設計】併發程式設計大合集-值得收藏Java程式設計
- Go 併發程式設計 - 併發安全(二)Go程式設計
- Golang 併發程式設計Golang程式設計
- 併發程式設計(四)程式設計
- 併發程式設計(二)程式設計
- java 併發程式設計Java程式設計
- golang併發程式設計Golang程式設計
- Java併發程式設計Java程式設計
- Go 併發程式設計Go程式設計
- shell併發程式設計程式設計
- Scala併發程式設計程式設計
- 併發程式設計 synchronized程式設計synchronized
- 併發程式設計和並行程式設計程式設計並行行程
- Java併發程式設計-鎖及併發容器Java程式設計
- 併發程式設計之:JUC併發控制工具程式設計
- Java併發系列—併發程式設計挑戰Java程式設計
- 【Java併發程式設計】一、為什麼需要學習併發程式設計?Java程式設計
- Java併發程式設計 - 第十一章 Java併發程式設計實踐Java程式設計
- java-併發程式設計Java程式設計
- 併發程式設計前傳程式設計
- 併發程式設計導論程式設計
- C# 併發程式設計C#程式設計
- Java併發程式設計-CASJava程式設計
- 併發程式設計之:ForkJoin程式設計
- 併發程式設計之:JMM程式設計
- 併發程式設計之:synchronized程式設計synchronized
- 併發程式設計之:Lock程式設計
- 併發程式設計之:CountDownLatch程式設計CountDownLatch
- Java併發程式設計:synchronizedJava程式設計synchronized
- Java 併發程式設計解析Java程式設計
- 併發程式設計進階程式設計
- 併發程式設計概覽程式設計
- Java併發程式設計:LockJava程式設計
- 併發程式設計---JMM模型程式設計模型
- 「聊聊併發程式設計」分享程式設計
- 併發程式設計—— LinkedTransferQueue程式設計
- 理解Golang併發程式設計Golang程式設計