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();
}
}
}