synchronized是併發編程中重要的使用工具之一,咱們必須學會使用而且掌握它的原理。java
JVM自帶的關鍵字,可在須要線程安全的業務場景中使用,來保證線程安全。編程
按照鎖的對象區分能夠分爲對象鎖和類鎖 按照在代碼中的位置區分能夠分爲方法形式和代碼塊形式安全
鎖對象爲當前this或者說是當前類的實例對象併發
public void synchronized method() {
System.out.println("我是普通方法形式的對象鎖");
}
public void method() {
synchronized(this) {
System.out.println("我是代碼塊形式的對象鎖");
}
}
複製代碼
鎖的是當前類或者指定類的Class對象。一個類可能有多個實例對象,但它只可能有一個Class對象。異步
public static void synchronized method() {
System.out.println("我是靜態方法形式的類鎖");
}
public void method() {
synchronized(*.class) {
System.out.println("我是代碼塊形式的類鎖");
}
}
複製代碼
參考 慕課網 《Java高併發之魂:synchronized深度解析》ide
最基本的用法在上一個標題用法中已將僞代碼列出,這裏列舉在以上基礎上稍微變化一些的用法 1.多個實例,對當前實例加鎖,同步執行,對當前類Class對象加鎖,異步執行高併發
public class SimpleExample implements Runnable {
static SimpleExample instance1 = new SimpleExample();
static SimpleExample instance2 = new SimpleExample();
@Override
public void run() {
method1();
method2();
method3();
method4();
}
public synchronized void method1() {
common();
}
public static synchronized void method2() {
commonStatic();
}
public void method3() {
synchronized(this) {
common();
}
}
public void method4() {
synchronized(MultiInstance.class) {
common();
}
}
public void method5() {
common();
}
public void method6() {
commonStatic();
}
public void common() {
System.out.println("線程 " + Thread.currentThread().getName() + " 正在執行");
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程 " + Thread.currentThread().getName() + " 執行完畢");
}
public static void commonStatic() {
System.out.println("線程 " + Thread.currentThread().getName() + " 正在執行");
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程 " + Thread.currentThread().getName() + " 執行完畢");
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("finished");
}
}
method1()、method3()結果爲:
線程 Thread-0 正在執行
線程 Thread-1 正在執行
線程 Thread-0 執行完畢
線程 Thread-1 執行完畢
finished method2()、method4()執行結果爲: 線程 Thread-0 正在執行 線程 Thread-0 執行完畢 線程 Thread-1 正在執行 線程 Thread-1 執行完畢 finished 複製代碼
2.對象鎖和類鎖,鎖的對象不同,互不影響,因此異步執行工具
// 將run方法改成
@Override
public void run() {
if("Thread-0".equals(Thread.currentThread().getName())) {
method1();
} else {
method2();
}
}
// 將main方法改成
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance1);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("finished");
}
結果爲:
線程 Thread-0 正在執行
線程 Thread-1 正在執行
線程 Thread-1 執行完畢
線程 Thread-0 執行完畢
finished
複製代碼
3.對象鎖和無鎖得普通方法,普通方法不須要持有鎖,因此異步執行優化
// 將run方法改成
@Override
public void run() {
if("Thread-0".equals(Thread.currentThread().getName())) {
method1();
} else {
method5();
}
}
// main方法同 2
結果爲:
線程 Thread-0 正在執行
線程 Thread-1 正在執行
線程 Thread-0 執行完畢
線程 Thread-1 執行完畢
finished
複製代碼
4.類鎖和無鎖靜態方法,異步執行this
// 將run方法改成
@Override
public void run() {
if("Thread-0".equals(Thread.currentThread().getName())) {
method1();
} else {
method6();
}
}
// main方法同 2
結果爲:
線程 Thread-0 正在執行
線程 Thread-1 正在執行
線程 Thread-0 執行完畢
線程 Thread-1 執行完畢
finished
複製代碼
5.方法拋出異常,synchronized鎖自動釋放
// run方法改成
@Override
public void run() {
if ("Thread-0".equals(Thread.currentThread().getName())) {
method7();
} else {
method8();
}
}
public synchronized void method7() {
...
throw new RuntimeException();
}
public synchronized void method8() {
common();
}
public static void main(String[] args) throws InterruptedException {
// 同 2
}
結果爲:
我是拋出異常的加鎖方法,我叫 thread---------1
我是沒有異常的加鎖方法,我叫 thread---------2
Exception in thread "thread---------1" java.lang.RuntimeException
at com.marksman.theory2practicehighconcurrency.synchronizedtest.SynchronizedException9.method1(SynchronizedException9.java:29)
at com.marksman.theory2practicehighconcurrency.synchronizedtest.SynchronizedException9.run(SynchronizedException9.java:15)
at java.lang.Thread.run(Thread.java:748)
thread---------2 運行結束
finished
// 這說明拋出異常後持有對象鎖的method7()方法釋放了鎖,這樣method8()才能獲取到鎖並執行。
複製代碼
6.可重入特性
public class SynchronizedRecursion {
int a = 0;
int b = 0;
private void method1() {
System.out.println("method1正在執行,a = " + a);
if (a == 0) {
a ++;
method1();
}
System.out.println("method1執行結束,a = " + a);
}
private synchronized void method2() {
System.out.println("method2正在執行,b = " + b);
if (b == 0) {
b ++;
method2();
}
System.out.println("method2執行結束,b = " + b);
}
public static void main(String[] args) {
SynchronizedRecursion synchronizedRecursion = new SynchronizedRecursion();
synchronizedRecursion.method1();
synchronizedRecursion.method2();
}
}
結果爲:
method1正在執行,a = 0
method1正在執行,a = 1
method1執行結束,a = 1
method1執行結束,a = 1
method2正在執行,b = 0
method2正在執行,b = 1
method2執行結束,b = 1
method2執行結束,b = 1
// 能夠看到method1()與method2()的執行結果同樣的,method2()在獲取到對象鎖之後,在遞歸調用時不須要等上一次調用先釋放後再獲取,而是直接進入,這說明了synchronized的可重入性.
// 固然,除了遞歸調用,調用同類的其它同步方法,調用父類同步方法,都是可重入的,前提是同一對象去調用,這裏就不一一列舉了.
複製代碼
總結一下
將下面兩段代碼分別用 javac *.java編譯成.class文件,再反編譯 javap -verbose *.class文件
public class SynchronizedThis {
public void method() {
synchronized(this) {}
}
}
// 反編譯結果
public void method();
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
複製代碼
public class SynchronizedMethod {
public synchronized void method() {}
}
// 反編譯結果
public synchronized void method();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 2: 0
複製代碼
能夠看到:
上面咱們提到monitor,這是什麼鬼? 其實,對象在內存是這樣存儲的,包括對象頭、實例數據和對齊填充Padding,其中對象頭包括 Mark Word和類型指針。
Mark Word用於存儲對象自身的運行時數據,如哈希碼(identity_hashcode)、GC分代年齡(age)、鎖狀態標誌(lock)、線程持有的鎖、偏向線程ID(thread)、偏向時間戳(epoch)等等,佔用內存大小與虛擬機位長一致。
Mark Word (32 bits) | State 鎖狀態 |
---|---|
identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal 無鎖 |
thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased 偏向鎖 |
ptr_to_lock_record:30 | lock:2 | Lightweight Locked 輕量級鎖 |
ptr_to_heavyweight_monitor:30 | lock:2 | Heavyweight Locked 重量級鎖 |
| lock:2 | Marked for GC GC標記 |
Mark Word (64 bits) | State 鎖狀態 |
---|---|
unused:25|identity_hashcode:31|unused:1|age:4|biased_lock:1|lock:2 | Normal 無鎖 |
thread:54 |epoch:2|unused:1|age:4|biased_lock:1|lock:2 | Biased 偏向鎖 |
ptr_to_lock_record:62 | lock:2 | Lightweight Locked 輕量級鎖 |
ptr_to_heavyweight_monitor:62 | lock:2 | Heavyweight Locked 重量級鎖 |
| lock:2 | Marked for GC GC標記 |
能夠看到,monitor就存在Mark Word中。
類型指針指向對象的類元數據metadata,虛擬機經過這個指針肯定該對象是哪一個類的實例。
biased_lock | lock | 狀態 |
---|---|---|
0 | 01 | 無鎖 |
1 | 01 | 偏向鎖 |
0 | 00 | 輕量級鎖 |
0 | 10 | 重量級鎖 |
0 | 11 | GC標記 |
jdk1.6以前synchronized是很重的,因此並不被開發者偏心,隨着後續版本jdk對synchronized的優化使其愈來愈輕量,它仍是很好用的,甚至ConcurrentHashMap在jdk的put方法都在jdk1.8時從ReetrantLock.tryLock()改成用synchronized來實現同步。 而且還引入了偏向鎖,輕量級鎖等概念,下面是偏向鎖和輕量級鎖的獲取流程
www.processon.com/diagraming/…
參考 《咕泡學院公開課》 提取碼:s6vx
若是一個線程獲取了偏向鎖,那麼若是在接下來的一段時間裏,若是沒有其餘線程來搶佔鎖,那麼獲取鎖的線程在下一次進入方法時不須要從新獲取鎖。
區別 | synchronized | ReentrantLock |
---|---|---|
靈活性 | 代碼簡單,自動獲取、釋放鎖 | 相對繁瑣,須要手動獲取、釋放鎖 |
是否可重入 | 是 | 是 |
做用位置 | 可做用在方法和代碼塊 | 只能用在代碼塊 |
獲取、釋放鎖的方式 | monitorenter、monitorexit、ACC_SYNCHRONIZED | 嘗試非阻塞獲取鎖tryLock()、超時獲取鎖tryLock(long timeout,TimeUnit unit)、unlock() |
獲取鎖的結果 | 不知道 | 可知,tryLock()返回boolean |
使用注意事項 | 一、鎖對象不能爲空(鎖保存在對象頭中,null沒有對象頭) 二、做用域不宜過大 |
一、切記要在finally中unlock(),不然會造成死鎖 二、不要將獲取鎖的過程寫在try塊內,由於若是在獲取鎖時發生了異常,異常拋出的同時,也會致使鎖無端被釋放。 |