概述
關鍵字synchronized可以修飾方法或者以同步程式碼塊的形式來進行使用,它主要確保多個執行緒在同一時刻只能有一個執行緒處於方法或者同步塊中,它保證了執行緒對變數訪問的可見性和排他性。
同步程式碼塊
public class SynchronizedDemo implements Runnable {
@Override
public void run() {
synchronized (this) {
System.out.println("當前執行緒:" + Thread.currentThread().getName());
try {
System.out.println("休眠2s");
Thread.sleep(2000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("當前執行緒:" + Thread.currentThread().getName() + "結束");
}
}
public static void main(String[] args) {
SynchronizedDemo instance = new SynchronizedDemo();
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
}
}
執行結果:
同步程式碼塊形式——鎖為this,兩個執行緒使用的鎖是一樣的,執行緒1必須要等到執行緒0釋放了該鎖後,才能執行。通過反編譯分析Synchornized關鍵字作用在同步程式碼塊時的實現原理,執行javap -v SynchronizedDemo.class,部分相關輸出如下所示:
可以看到對於同步程式碼塊,Synchronized使用了monitorenter和monitorexit指令。
同步方法
public class SynchronizedMethodDemo implements Runnable {
@Override
public void run() {
method();
}
// 修飾普通方法
public synchronized void method() {
System.out.println("當前執行緒:" + Thread.currentThread().getName());
try {
System.out.println("休眠2s");
Thread.sleep(2000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("當前執行緒:" + Thread.currentThread().getName() + "結束");
}
public static void main(String[] args) {
SynchronizedDemo instance = new SynchronizedDemo();
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
}
}
輸出結果
可以看到兩個執行緒使用的鎖是一樣的,執行緒1必須要等到執行緒0釋放了該鎖後,才能執行。Synchronized作用在普通方法時鎖物件預設為this。
執行反編譯指令,檢視輸出結果:
同步方法依靠方法修飾符上的ACC_SYNCHRONIZED完成。
總結
Synchronized本質上是對一個物件的監視器(monitor)進行獲取,而這個過程是排他的,也就是同一時刻只能有一個執行緒獲取到Synchroniezd所保護物件的監視器。任何一個物件都有自己的監視器,當這個物件由同步塊或者這個物件的同步方法呼叫時,執行方法的執行緒必須先獲取到該物件的監視器才能進入同步塊或者同步方法,而沒有獲取到監視器的執行緒將會被阻塞在同步塊和同步方法的入口處,進入阻塞狀態。