java並發學習--第八章 JDK 8 中線程優化的新特性


一、新增原子類LongAdder

  LongAdder是JDK8AtomicLong的增強工具類,它與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的特點:

  1. 獲取鎖的時候,會給獲取鎖的方法返回一個Stamp,當Stamp的值為0時,表示獲取失敗。當然釋放鎖的時候沒,釋放的方法中必須要有獲取鎖的時候得到的Stamp。這樣做的好處是能夠提供讀寫互斥的性能。
  2. StampedLock是不可重入鎖,如果兩個方法獲取了同一把鎖,那么就會發生死鎖;
  3. StampedLock中為我們提供了3把鎖:
    ①Reading(讀鎖):類似於ReentrantReadWriteLock的讀鎖
    ②Writing(寫鎖):類似於ReentrantReadWriteLock的寫鎖
    ③Optimistic reading(樂觀讀模式):這是StampedLock為我們提供的一把優化鎖
  4. StampedLock支持讀鎖和寫鎖的相互轉換
    我們知道RRW中,當線程獲取到寫鎖后,可以降級為讀鎖,但是讀鎖是不能直接升級為寫鎖的。
    StampedLock提供了讀鎖和寫鎖相互轉換的功能,使得該類支持更多的應用場景。
  5. 無論寫鎖還是讀鎖,都不支持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();
        
    }
}

運行結果:

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM