鎖優化的思路和方法:
減少鎖持有時間:
減少其他線程等待時間,只同步需要同步的相關的代碼;
減少鎖粒度:
將大對象拆成小對象,增加並行度,降低鎖競爭;
偏向鎖,輕量級鎖成功率提高;
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));
}
}