java高併發程序設計學習筆記九鎖的優化和注意事項

鎖優化的思路和方法:

減少鎖持有時間:

減少其他線程等待時間,只同步需要同步的相關的代碼;

減少鎖粒度:

將大對象拆成小對象,增加並行度,降低鎖競爭;

    偏向鎖,輕量級鎖成功率提高;

ConcurrentHashMap;

鎖分離:

根據功能進行鎖分離;

ReadWriteLock;

讀多寫少的情況,可提高性能;

讀寫分離思想延伸,只要操作互不影響,鎖就可以分離;

LinkedBlockingQueue:

隊列、鏈表;

從一端讀,另一端寫;


鎖粗化:

通常情況,爲保證多線程間有效併發,會要求每個線程持有鎖的時間儘量短,即使用完後立即釋放,

等待在這個鎖上的其他線程才能今早獲得資源;

但是,凡事有度,對一個鎖不停的請求、同步和釋放,其本身也會消耗系統寶貴資源,反而不利於性能的優化;


鎖消除:

在即時編譯器時,如果發現不可能被共享的對象,則可以消除其鎖操作;

比如某些情況的StringBuffer等,比如局部變量的情況;


虛擬機內的鎖優化方法:

基礎:對象頭MARK:

對象頭的編輯,32位;

描述對象的hash、鎖信息、垃圾回收標記、年齡;

指向鎖記錄指針、monitor指針、GC標記、偏向鎖線程ID;


偏向鎖: 

大部分情況是沒有競爭的,可通過偏向來提高性能;

所謂的偏向,就是偏心,即鎖會偏向於當前已經佔有鎖的線程;

將對象頭Mark的標記設置爲偏向,並將線程ID寫入對象頭Mark;

只要沒有競爭,獲得偏向鎖的線程在將來進入同步塊,不需要做同步

當其他線程請求相同的鎖時,偏向模式結束

-XX:+UseBiasedLocking,默認開啓;

在競爭激烈的場合,偏向鎖會增加系統負擔;


輕量級鎖:

*普通的鎖處理性能不夠理想,輕量級鎖是一種快速的鎖定方法;

*如果對象沒被鎖:將對象頭的MARK指針保存到鎖對象中;將對象設置爲指向鎖的指針(在線程棧空間中);

如果輕量級鎖失敗,表示存在競爭,升級爲重量級鎖(常規鎖);

在沒有鎖競爭的前提下,減少傳統鎖使用OS互斥量產生的性能損耗;

在競爭激烈時,輕量級鎖會做很多額外操作,導致性能下降;


自旋鎖:

當競爭存在時,如果線程可以很快獲得鎖,那麼可以不在OS層掛起線程,讓線程做幾個操作(自旋);

JDK1.6 中-XX:+UseSpinning開啓;

JDK1.7中,去掉此參數,改爲內置實現;

如果同步塊很長,自旋失敗,會降低系統性能;

如果同步塊很短,自旋成功,節省線程掛起切換時間,提升系統性能;

總結:

這幾種鎖不是java語音層面的鎖優化方法,jvm虛擬機層面的;

內置於JVM中獲取鎖的優化方法,和獲取鎖的步驟:

-偏向鎖可用會先嚐試偏向鎖;輕量級鎖可用會先嚐試輕量級鎖;

-以上都失敗,嘗試自旋鎖;再失敗則嘗試普通鎖,使用OS互斥量在在操作系統層掛起;


一個錯誤使用鎖的案例

看起來是沒有問題的,Integer是個不變的對象,不懂得線程把不同的i的引用賦給了i;


public class IntegerLock {

static Integer i=0;

public static class AddThread extends Thread{

public void run(){

for(int k=0;k<100000;k++){

synchronized(i){

i++;

}

}

}
}
public static void main(String[] args) throws InterruptedException {
AddThread t1=new AddThread();
AddThread t2=new AddThread();
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}


ThreadLocal及其源碼分析:

徹底把鎖去掉;

爲每一個線程提供一個對象實例;維護一個線程的局部變量;

源碼分析:每個線程都維護一份自己的ThreadLocal.ThreadLocalMap,從線程Thread中獲取的一個成員變量threadLocals;

key即是ThreadLocal本身,value是設置的值;


比如:SimpleDateFormat是非線程安全的,被多個線程操作時可能產生錯誤;

static ThreadLocal<SimpleDateFormat> tl=new ThreadLocal<SimpleDateFormat>();
public static class ParseDate implements Runnable{
int i=0;
public ParseDate(int i){this.i=i;}
public void run() {
try {
if(tl.get()==null){
tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
Date t=tl.get().parse("2015-03-29 19:29:"+i%60);
System.out.println(i+":"+t);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
for(int i=0;i<1000;i++){
es.execute(new ParseDate(i));
}
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章