java的轻量锁,jvm第7节-锁(偏向锁,轻量锁,自旋锁)
在介紹鎖之前我們先介紹一個線程不安全的例子,一個全局的list,開2個線程往里面插入數據,代碼如下:
package com.jvm.day6.lock.demo;
import java.util.ArrayList;
import java.util.List;
/**
* 測試都線程共享一個變量帶來的現象
* @Author:xuehan
* @Date:2016年3月20日下午3:35:29
*/
class NumberAdd implements Runnable{
public static List numberList =new ArrayList();
public static int startNum;
public NumberAdd(int startNum){
this.startNum = startNum;
}
@Override
public void run() {
int count = 0;
while(count < 1000000){
numberList.add(count ++ );
startNum = startNum + 2;
}
}
}
public class Test{
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(new NumberAdd(1));
Thread t2 = new Thread(new NumberAdd(0));
t1.start();
t2.start();
while(t1.isAlive() || t2.isAlive()){Thread.sleep(2);}
System.out.println("集合大小" + NumberAdd.numberList.size() );
System.out.println( "最后一個值的大" + NumberAdd.numberList.get(NumberAdd.numberList.size() - 1));
for(int i = NumberAdd.numberList.size() - 10 ; i < NumberAdd.numberList.size() -1; i ++){
System.out.println(NumberAdd.numberList.get(i));
}
}
}
按照開始想的,集合里面應該有200萬個數據了,結果卻出現了數組越界的錯誤,為什么呢,這是因為ArrayList不是線程安全的,用來存放數據的elementData是共享的, 線程A往list里添加數據的時候剛驗證大小通過,還沒有插入,然后輪到線程B執行,線程B剛好插入了list該擴容的最后一個元素,然后list滿了,線程A執行,A線程往集合里面插入元素,引起了數據越界。
jvm鎖
每個對象都一個mark頭,他的作用是:
Mark Word,對象頭的標記,32位
描述對象的hash、鎖信息,垃圾回收標記,年齡
指向鎖記錄的指針
指向monitor的指針
GC標記
偏向鎖線程ID
偏向鎖
jvm控制,可以設置jvm啟動參數
大部分情況是沒有競爭的,所以可以通過偏向來提高性能
所謂的偏向,就是偏心,即鎖會偏向于當前已經占有鎖的線程
將對象頭Mark的標記設置為偏向,并將線程ID寫入對象頭Mark
只要沒有競爭,獲得偏向鎖的線程,在將來進入同步塊,不需要做同步
當其他線程請求相同的鎖時,偏向模式結束
-XX:+UseBiasedLocking -jdk6需要手動打開
默認啟用
在競爭激烈的場合,偏向鎖會增加系統負擔
偏向鎖是系統自帶的設置參數開啟,java代碼如下:
package com.jvm.day6.lock;
import java.util.List;
import java.util.Vector;
/**
*使用 偏向鎖-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
* 不使用偏向鎖-XX:-UseBiasedLocking
* 根據測試下面代碼使用偏向鎖可提高150毫秒執行時間 150/2300,提高效率6%
* @Author:xuehan
* @Date:2016年3月19日下午12:06:15
*/
public class DeflectionLock {
public static List numberList =new Vector();
public static void main(String[] args) throws InterruptedException {
System.out.println((int)'l');
long begin=System.currentTimeMillis();
int count=0;
int startnum=0;
while(count<10000000){
numberList.add(startnum);
startnum+=2;
count++;
}
long end=System.currentTimeMillis();
System.out.println(end-begin);
}
} ?根據我的寫實偏向鎖可以提高性能6%
輕量級鎖
BasicObjectLock,這個鎖是嵌入到線程棧中的,他有兩部組成
BasicLock和
ptr to obj hold the lock,
BasicLock里面存放著對象頭,
ptr to obj hold the lock為指向持有對象鎖的指針
普通的鎖處理性能不夠理想,輕量級鎖是一種快速的鎖定方法。
如果對象沒有被鎖定
將對象頭的Mark指針保存到鎖對象中
將對象頭設置為指向鎖的指針(在線程棧空間中)
如果輕量級鎖失敗,表示存在競爭,升級為重量級鎖(常規鎖)
在沒有鎖競爭的前提下,減少傳統鎖使用OS互斥量產生的性能損耗
在競爭激烈時,輕量級鎖會多做很多額外操作,導致性能下降
自旋鎖
當競爭存在時,如果線程可以很快獲得鎖,那么可以不在OS層掛起線程,讓線程做幾個空操作(自旋)
JDK1.6中-XX:+UseSpinning開啟
JDK1.7中,去掉此參數,改為內置實現
如果同步塊很長,自旋失敗,會降低系統性能
如果同步塊很短,自旋成功,節省線程掛起切換時間,提升系統性能
上面的一些鎖不是Java語言層面的鎖優化方法
他們是內置于JVM中的獲取鎖的優化方法和獲取鎖的步驟
偏向鎖可用會先嘗試偏向鎖
輕量級鎖可用會先嘗試輕量級鎖
以上都失敗,嘗試自旋鎖
再失敗,嘗試普通鎖,使用OS互斥量在操作系統層掛起
我們可以從以下方面對鎖進行優化
減少鎖的時間
沒必須放在同步塊的代碼盡量不要放在代碼塊里
減少鎖的粒度
將大對象,拆成小對象,大大增加并行度,降低鎖競爭
偏向鎖,輕量級鎖成功率提高
實現的例子如ConcurrentHashMap
使用若干個Segment :Segment[] segments
Segment中維護HashEntry
put操作時
先定位到Segment,鎖定一個Segment,執行put
在減小鎖粒度后, ConcurrentHashMap允許若干個線程同時進入
鎖分離
根據功能進行鎖分離
ReadWriteLock
讀多寫少的情況,可以提高性能
讀寫分離思想可以延伸,只要操作互不影響,鎖就可以分離
LinkedBlockingQueue
隊列
鏈表
鎖粗化
通常情況下,為了保證多線程間的有效并發,會要求每個線程持有鎖的時間盡量短,即在使用完公共資源后,應該立即釋放鎖。只有這樣,等待在這個鎖上的其他線程才能盡早的獲得資源執行任務。但是,凡事都有一個度,如果對同一個鎖不停的進行請求、同步和釋放,其本身也會消耗系統寶貴的資源,反而不利于性能的優化
for(int i=0;i<1000;i++){
synchronized(lock){}
}
// 應該寫成
synchronized(lock){
for(int i =0; i < 1000; i ++){}
}
這時候我們要增加鎖的持有時間不要讓請求和釋放鎖頻繁的發生
鎖消除
在java方法體里如果不是共享的變量不需要同步操作的,這時候jvm會自動的優化把鎖去掉,如StingBuffer和Vector,使用鎖消除
-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
關閉鎖消除
-server -XX:+DoEscapeAnalysis -XX:-EliminateLocks
無鎖
鎖是悲觀的操作
無鎖是樂觀的操作
無鎖的一種實現方式
CAS(Compare And Swap),CAS是原子的
非阻塞的同步
CAS(V,E,N)
在應用層面判斷多線程的干擾,如果有干擾,則通知線程重試,一般這樣做會讓程序變的復雜,但性能更加好。
總結
以上是生活随笔為你收集整理的java的轻量锁,jvm第7节-锁(偏向锁,轻量锁,自旋锁)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php中延迟绑定,PHP静态延迟绑定
- 下一篇: java textfield 数字,如何