1. 程式人生 > >JDK1.8的併發新特性

JDK1.8的併發新特性

JDK1.8中有一些併發的新特性,可以提高變成的效率。本文寫的主要是LongAdder和stampedlock的特性。
多執行緒發生死鎖時dump檢視方式:
使用命令jps:如下所示
這裡寫圖片描述

通過這個命令我們可以得到死鎖號,然後再通過命令jstack檢視

如下所示:
這裡寫圖片描述

LongAdder

LongAdder是什麼?
在大資料處理過程,為了方便監控,需要統計資料,少不了原子計數器。為了儘量優化效能,需要採用高效的原子計數器。在jdk8中,引入了LongAdder,非常適合多執行緒原子計數器。
我們知道,AtomicLong已經是非常高效的了,涉及併發的地方都是使用CAS(無鎖)操作,在硬體層次上去做 compare and set操作。效率非常高。
LongAdder比AtomicLong更加高效。

實現原理:
LongAdder 沿用了concurrentMap原理,他是將1個整數拆分成一個數組cells,陣列中有若干個cell。若有多個線層,每個執行緒通過CAS更新其中的一個小cell。然後內部將陣列做sum求和操作得到整數的value;
這樣就使得AtomicLong的單一執行緒做CAS操作演變成多個執行緒同時做CAS操作,期間互不影響。從而提高效率;
LongAdder開始並沒有做拆分,當多執行緒間執行遇到衝突時才會拆分cell,若是多執行緒執行始終沒有衝突,則它相當於AtomicLong;

如何分配cell的???
拿到執行緒相關的HashCode物件後,獲取它的code變數,計算出一個在Cells 陣列中當前執行緒的HashCode對應的索引位置,並將該位置的Cell 物件拿出來用CAS更新它的value值。

LongAdder的繼承樹
這裡寫圖片描述

LongAdder的方法
這裡寫圖片描述

使用案例

import java.util.concurrent.atomic.LongAdder;

public class LongAdderTest {

    private static LongAdder la =new LongAdder();

    public static int a =0;
    public static void add(){
        la.increment();
        a++;
    }
    /**
     * @param args
     * @throws
InterruptedException */
public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub Thread t1 = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<10000;i++){ add(); } } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<10000;i++){ add(); } } }); t2.start(); t1.join();t2.join(); System.out.println("---la-----"+la); System.out.println("---a-----"+a); } }

執行結果:
這裡寫圖片描述

StampedLock

stampedLock推出了樂觀讀鎖,在使用樂觀讀鎖時,不會阻塞寫鎖,這使得我們在寫資料時,不會因為使用讀鎖而長時間的阻塞寫,從而提高效率;
ReentrantReadWriteLock 在沒有任何讀寫鎖時,才可以取得寫入鎖,這可用於實現了悲觀讀取(Pessimistic Reading),即如果執行中進行讀取時,經常可能有另一執行要寫入的需求,為了保持同步,ReentrantReadWriteLock 的讀取鎖定就可派上用場。
然而,如果讀取執行情況很多,寫入很少的情況下,使用 ReentrantReadWriteLock 可能會使寫入執行緒遭遇飢餓(Starvation)問題,也就是寫入執行緒遲遲無法競爭到鎖定而一直處於等待狀態。
StampedLock控制鎖有三種模式(寫,讀,樂觀讀),一個StampedLock狀態是由版本和模式兩個部分組成,鎖獲取方法返回一個數字作為票據stamp,它用相應的鎖狀態表示並控制訪問,數字0表示沒有寫鎖被授權訪問。在讀鎖上分為悲觀鎖和樂觀鎖。
所謂的樂觀讀模式,也就是若讀的操作很多,寫的操作很少的情況下,你可以樂觀地認為,寫入與讀取同時發生機率很少,因此不悲觀地使用完全的讀取鎖定,程式可以檢視讀取資料之後,是否遭到寫入執行的變更,再採取後續的措施(重新讀取變更資訊,或者丟擲異常) ,這一個小小改進,可大幅度提高程式的吞吐量!!
示例程式碼:

import java.util.concurrent.locks.StampedLock;

public class StampedLockTest {

    private static final StampedLock sl = new StampedLock();
    private static int x;
    private static int y;

    public static void move(int deltax,int deltay) throws InterruptedException{
        System.out.println("寫執行緒----"+Thread.currentThread().getName());
        long sw = sl.writeLock();//獲取寫鎖
        try{
            x = x+deltax;
            y = y+deltay;
        }finally{
            sl.unlockWrite(sw);//釋放寫鎖
        }
    }

    public static int distanceFromOrigin(){
        long sr = sl.tryOptimisticRead(); //獲取樂觀讀鎖,不會阻塞寫鎖
        int currentx = x;
        int currenty = y;
        //讀完成後驗證期間是否有寫操作改變了資料sl.validate(sr)為true則表示期間無寫操作,否則表示資料可能已經被改變
        System.out.println(currentx+"---第一次讀取資料-----"+currenty);
        if(!sl.validate(sr)){
            sr = sl.readLock(); //使用了悲觀鎖,會阻塞寫鎖
            try{
                System.out.println("悲觀鎖讀執行緒----------"+Thread.currentThread().getName());
                currentx = x;
                currenty = y;
            }finally{
                sl.unlockRead(sr);//釋放悲觀讀鎖
            }
        }else{
            System.out.println("樂觀鎖讀執行緒----------"+Thread.currentThread().getName());
        }
        System.out.println(currentx+"----第二次讀取資料----"+currenty);
        return currentx*currenty;
    }
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        for(int i=0;i<10;i++){
            final int q =i;
            new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        move(q,q+8);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        for(int i=0;i<50;i++){
            new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        distanceFromOrigin();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

}

這裡寫圖片描述