更多并發(fā)相關(guān)內(nèi)容,查看==>Java 線程&并發(fā)學(xué)習(xí)目錄
在上一篇Java 線程 和 鎖 基礎(chǔ)知識(shí)已經(jīng)介紹了Java中的線程和鎖的一些基本概念竣蹦,現(xiàn)在就來(lái)學(xué)習(xí)和了解下Java的內(nèi)置鎖synchronized。具體包含如下幾個(gè)點(diǎn):
- 類鎖和對(duì)象鎖的用法以及同異;
- synchronized的優(yōu)化,通過(guò)對(duì)象的頭部結(jié)構(gòu)了解和學(xué)習(xí)偏向鎖、輕量級(jí)鎖范抓、重量級(jí)鎖纸型;
- 不同的synchronized指令差異以及其說(shuō)明拇砰。
synchronized是Java原生的悲觀鎖、具有可重入的特性狰腌,可保證共享數(shù)據(jù)的線程安全除破。使用時(shí)需要和具體的對(duì)象或類關(guān)聯(lián)綁定。JDK1.5開(kāi)始琼腔,為了提高效率瑰枫,在不同的競(jìng)爭(zhēng)沖突情境下,synchronized也會(huì)出現(xiàn)從無(wú)鎖->偏向鎖->輕量級(jí)鎖->重量級(jí)鎖的單向鎖轉(zhuǎn)變丹莲。
1光坝、synchronized 使用
synchronized可以在對(duì)象尸诽、類以及代碼塊等地方使用,只要不出現(xiàn)活躍性以及發(fā)布不安全等問(wèn)題盯另,一般情況下可以確保單JVM上的共享數(shù)據(jù)安全性含。
對(duì)象使用
public class SynchronizedDemo {
private Object OBJECT = new Object();
// 鎖標(biāo)識(shí),誰(shuí)占有該對(duì)象就表示占據(jù)該鎖了
public void testFunction() {
System.out.println(Thread.currentThread().getName() + " testFunction");
}
public synchronized void testSynchronizedFunction() {
// 對(duì)象方法鎖
System.out.println(Thread.currentThread().getName() + " testSynchronizedFunction");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void testSynchronizedObject() {
// 對(duì)象代碼塊鎖
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " testSynchronizedObject");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void testSynchronizedDifferentObject() {
// 對(duì)象代碼塊鎖土铺,關(guān)聯(lián)的是OBJECT這個(gè)對(duì)象
synchronized (OBJECT) {
System.out.println(Thread.currentThread().getName() + " testSynchronizedDifferentObject");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void testSynchronizedObjectAgain() {
// 對(duì)象代碼塊鎖胶滋,重入操作
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " testSynchronizedObjectAgain");
testSynchronizedFunction();
}
}
}
再看看下面的測(cè)試demo的效果如何
public class SynchronizedTest {
public static void testObject() {
// 同一個(gè)demo,使用對(duì)象鎖的時(shí)候悲敷,只有不是執(zhí)行同一個(gè)
SynchronizedDemo demo = new SynchronizedDemo();
Runnable runnable1 = () -> demo.testSynchronizedFunction();
Runnable runnable2 = () -> demo.testSynchronizedObject();
Runnable runnable3 = () -> demo.testSynchronizedFunction();
new Thread(runnable1, "run1").start();
new Thread(runnable2, "run2").start();
// new Thread(runnable3, "run3").start();
}
public static void testObject1() {
// 不同的demo究恤,使用對(duì)象鎖的時(shí)候,各自無(wú)影響
// 因?yàn)殒i住的是對(duì)象后德,不同的對(duì)象之間是隔離開(kāi)的
SynchronizedDemo demo = new SynchronizedDemo();
SynchronizedDemo demo1 = new SynchronizedDemo();
Runnable runnable = () -> demo.testSynchronizedFunction();
Runnable runnable1 = () -> demo1.testSynchronizedFunction();
new Thread(runnable, "run").start();
new Thread(runnable1, "run1").start();
}
public static void testObjectAgain() {
// 同一個(gè)demo部宿,使用對(duì)象鎖后,可以再重入
SynchronizedDemo demo = new SynchronizedDemo();
Runnable runnable1 = () -> demo.testSynchronizedObjectAgain();
Runnable runnable2 = () -> demo.testSynchronizedFunction();
new Thread(runnable2, "run2").start();
new Thread(runnable1, "run1").start();
}
public static void main(String[] args) {
SynchronizedTest.testObject();
//SynchronizedTest.testObject1();
//SynchronizedTest.testObjectAgain();
}
}
如上main方法中的不同方法調(diào)用瓢湃,輸出的內(nèi)容基本差不多理张,主要是觀察其睡眠暫停的時(shí)間
-
public synchronized
:寫在普通的方法上的就表示為「普通同步方法」,他是和當(dāng)前對(duì)應(yīng)的對(duì)象綁定在一起的绵患,不同的線程在調(diào)用同一個(gè)對(duì)象的該方法時(shí)會(huì)發(fā)生競(jìng)爭(zhēng)沖突雾叭,不同對(duì)象則不會(huì)出現(xiàn)競(jìng)爭(zhēng) -
synchronized (this)
:寫在代碼塊中的,整體而言和普通方法沒(méi)有本質(zhì)的區(qū)別落蝙,只是和普通方法相比织狐,鎖粒度更細(xì)一些,效率(可能)更高些 -
synchronized (Object)
:寫在代碼塊中的筏勒,這個(gè)鎖就脫離了當(dāng)前對(duì)象綁定關(guān)系而是和 Object對(duì)象 關(guān)聯(lián)綁定移迫,幾個(gè)不同的類甚至可以通過(guò)傳入同一個(gè)Object實(shí)現(xiàn)不同對(duì)象見(jiàn)的鎖控制,此方法在很多源碼中也被大量使用管行,也建議使用 - 最后又提及到了可重入厨埋,一個(gè)線程在獲取到鎖后,再獲取該鎖則可以直接獲取捐顷。不過(guò)需要控制好可重入的順序荡陷,如果順序沒(méi)有控制好,再加上資源分配不恰當(dāng)迅涮,會(huì)引發(fā)死鎖的危險(xiǎn)(notify方法也會(huì)引發(fā)死鎖)亲善。
類使用
public class SynchronizedDemo {
public synchronized static void testStaticFunction() {
// 類靜態(tài)方法鎖
System.out.println(Thread.currentThread().getName() + " testStaticFunction");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void testClass() {
synchronized (SynchronizedDemo.class) {
System.out.println(Thread.currentThread().getName() + " testClass");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
demo的測(cè)試用例
public class SynchronizedTest {
public static void testClass() {
// 同一個(gè)類,使用類鎖
SynchronizedDemo demo = new SynchronizedDemo();
Runnable runnable1 = () -> SynchronizedDemo.testStaticFunction();
Runnable runnable2 = () -> demo.testClass();
new Thread(runnable2, "run2").start();
new Thread(runnable1, "run1").start();
}
public static void testClass2() {
// 一個(gè)類鎖 一個(gè)對(duì)象鎖逗柴,兩者不會(huì)起沖突
SynchronizedDemo demo = new SynchronizedDemo();
Runnable runnable1 = () -> demo.testClass();
Runnable runnable2 = () -> demo.testSynchronizedObject();
new Thread(runnable1, "run1").start();
new Thread(runnable2, "run2").start();
}
}
-
public synchronized static
:靜態(tài)方法,和當(dāng)前的類綁定關(guān)聯(lián)顿肺,同一個(gè)類在調(diào)用類似方法時(shí)戏溺,會(huì)出現(xiàn)競(jìng)爭(zhēng)沖突 -
synchronized (XXX.class)
:綁定的是指定的類XXX.class渣蜗,存在幾個(gè)不同的對(duì)象,方法中使用同一個(gè)類的情況 - 同一個(gè)對(duì)象的類鎖和對(duì)象鎖之間不會(huì)出現(xiàn)競(jìng)爭(zhēng)沖突
2旷祸、synchronized 優(yōu)化
JVM結(jié)構(gòu)分為程序計(jì)數(shù)器
耕拷、虛擬機(jī)棧
、本地方法棧
托享、方法區(qū)
以及堆
骚烧,而創(chuàng)建的對(duì)象信息則是存放在堆中
JVM結(jié)構(gòu)
虛擬機(jī)棧:對(duì)象的方法調(diào)用臨時(shí)申請(qǐng)的數(shù)據(jù)存放點(diǎn)、方法接口等信息闰围,A方法調(diào)用B方法赃绊,再調(diào)用C方法,這些關(guān)系就是存放在虛擬機(jī)棧中的羡榴,日常所說(shuō)的
打印出錯(cuò)誤的堆棧信息
也就存在棧中
本地方法棧:方法調(diào)用的本地native方法
方法區(qū):線程共享的區(qū)域(永生代)碧查,存儲(chǔ)類加載器加載的類信息、常量校仑、靜態(tài)變量等信息忠售,例如static和final
堆:對(duì)象實(shí)例存放點(diǎn)(包含新生代和老年代),新建的對(duì)象信息都是存放在堆中的
程序計(jì)數(shù)器:可以認(rèn)為是下一條需要執(zhí)行的指令指示器
對(duì)象堆的組成區(qū)域如下圖迄沫,其中數(shù)據(jù)實(shí)例是類的具體內(nèi)容稻扬,而對(duì)齊填充則是JVM的約定,所有對(duì)象的大小必須是8字節(jié)的倍數(shù)羊瘩,例如某個(gè)對(duì)象包含對(duì)象頭是63個(gè)字節(jié)泰佳,那么對(duì)齊填充則是1個(gè)字節(jié)。而和synchronized最密切的是對(duì)象頭中的MarkWord 標(biāo)記字段困后。
在標(biāo)記字段值也包含了很多內(nèi)容乐纸,例如HashCode,鎖標(biāo)志位等等摇予。具體如下圖在不同的鎖情況下汽绢,64位的MarkWord內(nèi)容。隨著競(jìng)爭(zhēng)的加大侧戴,synchronized會(huì)從無(wú)鎖->偏向鎖->輕量級(jí)鎖->重量級(jí)鎖轉(zhuǎn)變的
該圖來(lái)源自:https://blog.csdn.net/scdn_cp/article/details/86491792
- 無(wú)鎖:鎖對(duì)象剛剛創(chuàng)建宁昭,沒(méi)有競(jìng)爭(zhēng),偏向鎖標(biāo)識(shí)位為0酗宋,鎖狀態(tài)是01
- 偏向鎖:出現(xiàn)一個(gè)線程競(jìng)爭(zhēng)积仗,則直接把當(dāng)前的線程信息記錄到當(dāng)前對(duì)象中,并且只偏愛(ài)蜕猫,同時(shí)偏向鎖標(biāo)識(shí)位是為1
- 輕量級(jí)鎖:出現(xiàn)大于等于2個(gè)線程競(jìng)爭(zhēng)時(shí)寂曹,就不再偏愛(ài)了,鎖從偏向鎖升級(jí)為輕量級(jí)鎖,并記錄下競(jìng)爭(zhēng)成功的線程記錄隆圆,鎖狀態(tài)是00
- 重量級(jí)鎖:競(jìng)爭(zhēng)更加嚴(yán)重漱挚,鎖升級(jí)為重量級(jí)鎖(也叫同步鎖),現(xiàn)在MarkWord中指向的不再是線程信息渺氧,而是Monitor監(jiān)視器信息旨涝,同時(shí)鎖狀態(tài)是10
- 被GC標(biāo)記的對(duì)象:待回收了,只要下一次GC不再被引用就會(huì)被回收掉的侣背,鎖狀態(tài)是11
- 監(jiān)視器Monitor:和每一個(gè)對(duì)象都有一根無(wú)形的線關(guān)聯(lián)著白华,監(jiān)視器記錄著關(guān)聯(lián)的對(duì)象、持有的線程贩耐、阻塞的線程信息等
3弧腥、synchronized 底層實(shí)現(xiàn)
java 文件通過(guò)編譯后生成了class文件,再使用javap -verbose XXXX
文件輸出字節(jié)碼憔杨,為了便于說(shuō)明問(wèn)題新加非常小的demo文件測(cè)試一下
public class SimpleClass {
private Object obj = new Object();
public synchronized void run() {
// 同步方法
}
public void run1() {
// 同步代碼塊
synchronized (this) {}
}
public void run2() {
// 同步指定的對(duì)象
synchronized (obj) {}
}
public void run3() {
// 同步指定的類
synchronized (SimpleClass.class) {}
}
}
其中run() 和 run1() 從功能上來(lái)說(shuō)是完全一致的鸟赫,都是綁定當(dāng)前對(duì)象,查看相關(guān)指令如下代碼(除去了無(wú)關(guān)指令)
public synchronized void run();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=1, args_size=1
0: return
....
public void run1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: aload_1
5: monitorexit
6: goto 14
9: astore_2
10: aload_1
11: monitorexit
12: aload_2
13: athrow
14: return
.....
雖然這兩者的功能完全一致消别,但是具體的底層實(shí)現(xiàn)卻不一樣抛蚤,同步方法是直接添加了flagACC_SYNCHRONIZED
標(biāo)識(shí)其是一個(gè)同步的方法,而同步代碼塊則是使用了1條monitorenter指令和2條monitorexit指令寻狂,其中有2條monitorexit的原因主要是編譯器自動(dòng)產(chǎn)生一個(gè)異常處理器岁经,后面一個(gè)monitorexit就是在異常處理結(jié)束后釋放monitor的
public void run2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: getfield #3 // Field obj:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: aload_1
8: monitorexit
9: goto 17
12: astore_2
13: aload_1
14: monitorexit
15: aload_2
16: athrow
17: return
...
public void run3();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: ldc #4 // class new2019/Synchronized/SimpleClass
2: dup
3: astore_1
4: monitorenter
5: aload_1
6: monitorexit
7: goto 15
10: astore_2
11: aload_1
12: monitorexit
13: aload_2
14: athrow
15: return
....
而和run1()相比,run2()中的指令就僅僅只多了一句指令1: getfield #3
蛇券,獲取被管理的對(duì)象object缀壤,用來(lái)替換默認(rèn)的this,run3()的指令更加簡(jiǎn)單直接就是0: ldc #4
,把#4(SimpleClass.class)推送到了當(dāng)前的棧頂
這樣看來(lái)使用synchronized(XX)的方法從底層指令而言沒(méi)有太大的差異纠亚,就是加載了不同的數(shù)據(jù)進(jìn)行處理塘慕,有的是當(dāng)前對(duì)象,有的是指定對(duì)象蒂胞,有的是指定的類信息图呢,但是因?yàn)榧虞d的數(shù)據(jù)不同,使得持有的鎖也是完全不一樣的骗随,類對(duì)象會(huì)持有關(guān)聯(lián)一個(gè)監(jiān)視器蛤织,類Class也會(huì)持有一個(gè)監(jiān)視器
關(guān)于Monitor和MarkWord的C++底層實(shí)現(xiàn)原理可以看看HostSpot源碼