背景知識補充:
- 局部變量是線程安全的,局部變量屬於線程私有的,在線程間不共享
- 類變量是非線程安全的,在多個實例之間、多個線程之間都是共享的
- 實例變量屬於對象,在多個對象間不共享,當多個線程操作同一個對象的實例變量時,該變量將在多個線程中共享,則會導致非線程安全問題,但當多個線程分別操作同一個類的不同對象時,該變量只存在於當前線程中,故不會導致非線程安全問題
爲了解決線程不安全的問題,java中提供了synchronized
關鍵字,synchronized
可以用於修飾方法或修飾代碼塊
synchronized 同步方法
其語法格式就是在方法前面加上synchronized
關鍵字,如下:
synchronized public void methodName(){}
適用場景
當該方法內部有對共享資源進行讀寫操作時該方法就需要使用synchronized
關鍵字修飾
synchronized
取得的鎖是對象鎖,當一個線程調用對象的一個同步方法時,該線程將持有該對象的對象鎖,其它線程可以直接調用該對象中沒有被synchronized
關鍵字修飾的方法,其它線程若要調用該對象中被synchronized
關鍵字修飾的方法時,需要等待前一個線程釋放該對象的對象鎖後方可調用,在此之前只能排隊等待
如果多個線程訪問同一個類的多個對象,則JVM會創建多個鎖
這裏說的會創建多個鎖,那這些鎖是在什麼時候創建的呢?
鎖重入
前面背景知識提到當一個線程持有某個對象的對象鎖時,其它線程若想獲得該對象的對象鎖則需要等待前一個線程釋放對象鎖後纔可以獲得。
現在我們來分析一個例子
有個類中含有兩個被synchronized
關鍵字修飾的方法,分別爲methodA、methodB,methodA中調用methodB,此時程序還能正常執行嗎?
public class Services {
synchronized public static void methodA(){
System.out.println("methodA");
new Services().methodB();
}
synchronized public void methodB(){
System.out.println("methodB");
methodC();
}
synchronized public void methodC(){
System.out.println("methodC");
}
}
public class MyThread extends Thread {
@Override
public void run() {
super.run();
Services services = new Services();
services.methodA();
}
}
public class Run {
public static void main(String[] args){
MyThread myThread = new MyThread();
myThread.start();
}
}
這裏涉及到鎖重入
的概念,那什麼是鎖重入
呢?
當一個線程得到某對象的對象鎖後,在還未釋放該對象鎖時該線程再次請求獲取該對象的對象鎖,是可以再次得到該對象的對象鎖。即synchronized
方法、synchronized
代碼塊的內部調用本類的其它synchronized
方法、synchronized
代碼塊時,是永遠可以得到對象鎖的。
鎖重入也適用於有父子關係的繼承類中
鎖釋放
當synchronized方法、synchronize代碼塊執行完畢後該線程持有的對象鎖將自動釋放,除此之外,當synchronized方法、synchronized代碼塊中出現異常時,該線程持有的對象鎖也將自動釋放
synchronized關鍵字不具有繼承性
父類中的synchronized方法在子類中重寫時去掉了synchronized關鍵字,則子類中的該方法不具有同步功能