Java 并發鎖
1 為什么要加鎖
所先JVM將內存劃分成2個區域
- 主內存:所有線程共享的內存區域,存儲所有的共享變量
- 工作內存:每個線程獨有的內存區域,存儲該線程使用到的共享變量的副本
線程對變量的操作(讀取、賦值)必須在工作內存完成,從主內存讀取變量到工作內存,在對工作內存的變量進行操作,返回主內存。因此,當不同線程同時操作一個變量時,就會出現操作的數據實際值和主內存不一致。
2 需要解決那些問題
2.1 可見性問題
當一個共享變量被其他線程改變值后,其他線程無法立即感知到這個變量的值。
2.2 原子性
無法確保對同一個共享變量操作是原子性的。
2.3 有序性
操作系統可能對一些操作指令進行重新排序,從而提高運行速度。例如一個對象賦值操作。 A a = new A()。 單例模式時,會判斷對象是否是空對象,所有要兩次判斷非空
原本順序:
1. 分配內存空間
2. 初始化對象
3. 將引用指向內存
重排序后順序:
1. 分配內存空間
3. 初始化對象
3 鎖升級過程
虛擬機對象頭分為兩部分信息:
- 第一部分:用于存儲對象自身運行時數據,32BIts(32位系統)或者64Bits(64位系統),也稱為Mark Work, 其中有2Bits用來存儲鎖標識。
- 第二部分:用于存儲指向方法區對象類型數據的指針
鎖優化過程:
1. 鎖消除
如果不會出現同步問題,java編譯之后,就會忽略同步
2. 偏向鎖(只記錄該線程ID)
偏向鎖就是在無競爭(只有一個線程)的情況下把整個同步都消除。當鎖對象第一次被線程獲取的時候,虛擬機將對象頭中的標志位設位01,偏向模式。同時使用CAS把獲取到這個鎖的線程ID記錄在對象的Mark Word之中,如果CAS操作成功,以后該線程在進入時,都不需要在進行相關的鎖操作。
3. 輕量級鎖(一個一個來, 很慢, 不會有競爭, 能更換成功)
在無競爭的情況下使用CAS消除數據在同步使用的互斥量。CAS將對象的MarkWord更新為指向Lock Record(線程堆棧,用于存儲MarkWord的拷貝)的指針,如果更新成功,將對象頭Mark Word里面的鎖標志位更新成00。
4. 鎖粗化
如果一個代碼快有特別多的同步,虛擬機就會執行鎖粗化,因為其實對多個對象加鎖其實比一個對象加鎖開銷大的很多,雖然可能每個線程等待的時間會少點。
5. 自旋鎖和自適應自旋
當獲取不到鎖時,不在掛起,而是讓他進行自旋等待。自旋等待次數通過-XX:PreBlockSpin來改變。jdk1.6引入自適應自旋鎖。自適應意味著自旋的時間不在固定,根據任務執行的長短來決定自旋等待時常。
5. 鎖的公平性
- 公平鎖:先請求的線程先獲得鎖,后請求的線程進入等待隊列排隊,直到前面的線程釋放鎖后再依次獲取
- 非公平鎖:新請求鎖的線程先嘗試拿鎖,如果拿到直接獲取到鎖,而無需排隊等待,即使等待隊列中已有線程在排隊。
4 常見鎖類型
2.1 Synchronized
synchronized 能解決資源的原子性、可見性和有序性。底層實現依賴JVM的鎖機制和對象頭結構。JVM層面的鎖,自動加鎖和釋放鎖,非公平鎖,設計到鎖升級。
public class SynchronizedDemo {
int i = 0;
public static void main(String[] args) {
SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
synchronizedDemo.init1();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(synchronizedDemo.i);
}
public void init1() {
Thread[] threads = new Thread[100];
for (int i = 0; i < 100; i++) {
threads[i] = new Thread(this::fun1);
threads[i].start();
}
}
/**
* 方法上鎖
*/
synchronized void fun1() {
i++;
}
}
2.2 ReentranLock

浙公網安備 33010602011771號