對synchronized的理解和Spring為什么是單例的
只有真正理解了Java中對象是什么,才能理解這個關鍵字是什么意思
字面解釋
Java Guide中如此解釋:
synchronized 關鍵字解決的是多個線程之間訪問資源的同步性,synchronized關鍵字可以保證被它修飾的方法或者代碼塊在任意時刻只能有一個線程執行。
測試
但是這句話很多時候是有誤導性的,synchronized這個關鍵字并不能保證同一時間只有一個線程訪問,確切說,如果是用同一個對象調用方法的時候,方法的確是同一時間只能有一個線程訪問:
class Solution {
public static void main(String[] args) throws Exception {
Node node1 = new Node();
Node node2 = new Node();
new Thread(node1::test).start();
new Thread(node1::test).start();
System.out.println("主線程結束");
}
}
class Node {
public synchronized void test() {
System.out.println("!!!!!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成!!!");
}
}
輸出為
!!!!!!
主線程結束
完成!!!
!!!!!!
完成!!!
沒有問題,一個時間走一個線程。
但是,如果你用兩個不同的對象調用同一個方法,synchronized關鍵字是無用的:
class Solution {
public static void main(String[] args) throws Exception {
Node node1 = new Node();
Node node2 = new Node();
new Thread(node1::test).start();
new Thread(node2::test).start();
System.out.println("主線程結束");
}
}
class Node {
public synchronized void test() {
System.out.println("!!!!!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成!!!");
}
}
輸出為:
!!!!!!
!!!!!!
主線程結束
完成!!!
完成!!!
這時候為什么同步監視器沒用了呢?原因在于,synchronized加在普通方法的時候,當一個線程訪問這個方法的時候,持有的是當前類實例對象的同步監視器,也就是說當node1調用test()的時候,node1本身被他自己的線程new Thread持有了。這時候如果node2再次調用test(),由于node2自己沒有被任何線程持有,所以synchronized此時是失效的。如果是用node1對象調用調用了test(),這時node1被線程1持有以后,第二個new出來的線程是無法持有node1的,就只能等待。
所以一切都基于對Java“對象”這個概念的理解。
當synchronized加在靜態方法上的時候,線程持有的是類的Class對象:
class Solution {
public static void main(String[] args) throws Exception {
Node node1 = new Node();
Node node2 = new Node();
// new Thread(node1::test).start();
// new Thread(node2::test).start();
new Thread(() -> {
node1.test();
}).start();
new Thread(() -> {
node2.test();
}).start();
System.out.println("主線程結束");
}
}
class Node {
public static synchronized void test() {
System.out.println("!!!!!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成!!!");
}
}
這種情況下,即便兩次調用test()方法屬不同的對象,但是,由于線程持有的clazz對象是單例的,所以依然達到了同步的效果:
!!!!!!
主線程結束
完成!!!
!!!!!!
完成!!!
對于同步代碼塊,也是一樣的操作,一般情況下我們會將同步代碼塊中傳入this,就類似于將方法調用者的對象作為了同步監視器,這樣的操作在單例模式的基礎上是可以達到同步效果的。
Spring為什么是單例的
所以從這里可以看出,Spring為什么會把組件都設置為單例的呢?一方面Spring中各個組件的功能實現不需要多實例,請求和請求之間方法調用多為無狀態的,當多個查詢數據庫的請求調用到DAO層的時候,自然有mybatis幫我們實現代理類的不同對象去隔離不同請求的數據,在Controller和Service構建多實例對象浪費內存空間;另一方面,單例是有助于實現同步效果的。當我們在控制器接口的方法聲明為synchronized,這時用這個controller調用這個方法的時候,默認是用controller對象自己作為同步監視器的,而controller對象自然是滿足單例的,這樣就自然滿足了同步的要求。
本文來自博客園,作者:imissinstagram,轉載請注明原文鏈接:http://www.rzrgm.cn/LostSecretGarden/p/16268982.html

浙公網安備 33010602011771號