一、新增原子類LongAdder
LongAdder是JDK8中AtomicLong的增強工具類,它與
AtomicLong
最大的不同就是:在多線程場景下,LongAdder中對單一的變量進行拆分成多個變量,這些變量分為兩類base和Cell。base是基礎值,默認一般為0;而Cell就是我們所拆分的值,它可以有多個。所以當獲取LongAdder的值時就是把base
和每個Cell的值相加。
為什么要拆分成多個Cell
呢?這是因為在多線程場景下,如果多個線程都在對同一個變量進行操作,為了使這個變量原子性,我們不得不對起加鎖,這樣就大大的降低了程序的性能。但是如果把這個變量拆分為多個Cell,雖然還是會給每個Cell加鎖,但是線程訪問的變量就是不是同一個了,可以進行異步操作。
關於Cell的特點:
1.Cell采用懶加載機制,這是因為Cell占的內存空間相對比較大的。開始只會創建Base,只有當有其他線程來競爭資源時,才會拆分為多個Cell;
2.Cell的初始化值為2,每次擴容是2的N次方;
3.Cell本質是一個數組,它的元素最大值為 CPU 核數;
4.Cell 擴容條件: casCellsBusy 為 false 沒有庫容;有線程競爭資源;cell的數量沒有超過 CPU的核數。
我們來看一個例子,用LongAdder聲明一個值,使這個值加10000*10次,10為線程數:
public class ThreadLongAdder implements Runnable { //給線程方法傳遞一個參數 static LongAdder count = new LongAdder(); /** * 線程任務,將count的值相加10000*10次 */ public void run() { System.out.println("當前線程獲取count的值為:" + count); for (int i = 0; i < 10000; i++) { long num = 1l; count.add(num); } } public static void main(String[] args) throws InterruptedException { //創建多線程環境,這里創建了10個線程 Thread[] thread = new Thread[10]; //未創建的多線程中添加線程任務 for (int i = 0; i < 10; i++) { thread[i] = new Thread(new ThreadLongAdder()); } //啟動每個線程任務 for (int i = 0; i < 10; i++) { thread[i].start(); } //join方法的作用是阻塞主線程,防止還沒有計算完成,就開始輸出count的值了 for (int i = 0; i < 10; i++) { thread[i].join(); } System.out.println("count計算的結果是:" + count); } }
結果為:
二、增強鎖stampedlock
stampedlock是JDK8中新增的加強讀寫鎖。我們知道在高並發場景下,讀寫鎖中讀鎖與寫鎖是互斥的,如果當環境中讀的操作過多,寫的較少,就會導致寫操作的線程產生飢餓現象。
對於飢餓現象我們一般會考慮使用公平鎖,但是公平鎖會大大降低程序的性能。所以為了解決這一問題,JDK8為我們新增了stampedlock來解決。
stampedlock的特點:
- 獲取鎖的時候,會給獲取鎖的方法返回一個Stamp,當Stamp的值為0時,表示獲取失敗。當然釋放鎖的時候沒,釋放的方法中必須要有獲取鎖的時候得到的Stamp。這樣做的好處是能夠提供讀寫互斥的性能。
- StampedLock是不可重入鎖,如果兩個方法獲取了同一把鎖,那么就會發生死鎖;
- StampedLock中為我們提供了3把鎖:
①Reading(讀鎖):類似於ReentrantReadWriteLock的讀鎖
②Writing(寫鎖):類似於ReentrantReadWriteLock的寫鎖
③Optimistic reading(樂觀讀模式):這是StampedLock為我們提供的一把優化鎖 - StampedLock支持讀鎖和寫鎖的相互轉換
我們知道RRW中,當線程獲取到寫鎖后,可以降級為讀鎖,但是讀鎖是不能直接升級為寫鎖的。
StampedLock提供了讀鎖和寫鎖相互轉換的功能,使得該類支持更多的應用場景。 - 無論寫鎖還是讀鎖,都不支持Conditon等待
我們來看一個stampedlock使用的例子:
線程任務類:
public class StampedlockDemo extends Thread{ StampedLock stampedLock = new StampedLock(); int num; static int sum; /*** * 這是線程任務,先寫再讀 */ @Override public void run() { // 獲取寫鎖,Long類型表示Stamp的值,如果是0就獲取失敗 Long write = stampedLock.writeLock(); if (write != 0L) { System.out.println("現在進行寫的操作"); // 要寫的方法 sum += num; } // 轉換為讀鎖 Long read = stampedLock.tryConvertToOptimisticRead(write); if (read != 0L) { // 要讀的內容 System.out.println("現在進行的讀操作,當前的值為:"); System.out.println(sum); } } public StampedlockDemo(StampedLock stampedLock, int num) { super(); this.stampedLock = stampedLock; this.num = num; } }
測試類:
public class Main { public static void main(String[] args) { //創建鎖對象 StampedLock stampedLock = new StampedLock(); //創建三個線程 StampedlockDemo stampedlockDemo1=new StampedlockDemo(stampedLock,1); StampedlockDemo stampedlockDemo2=new StampedlockDemo(stampedLock,10); StampedlockDemo stampedlockDemo3=new StampedlockDemo(stampedLock,100); stampedlockDemo3.start(); stampedlockDemo1.start(); stampedlockDemo2.start(); } }
運行結果: