注意
以下內(nèi)容源自互聯(lián)網(wǎng)相關(guān)資料及本人學(xué)習(xí)與工作經(jīng)驗演训,僅為學(xué)習(xí)及技術(shù)分享所用岛抄,切勿用于商業(yè)用途,轉(zhuǎn)載請注明出處。
1. 線程同步
多個線程之間可能會競爭相同的資源录肯,比如兩個讀取數(shù)據(jù)庫操作的線程就會競爭數(shù)據(jù)庫連接這個資源驴一,這個被競爭的資源叫作共享資源或者臨界資源梳虽。如何協(xié)調(diào)各個線程對共享資源的使用楼入,稱為線程同步。線程同步需要考慮以下幾點:
- 存在競爭就需要同步要销,同步的概念指的不是同時執(zhí)行构回,而是協(xié)同步調(diào),按一定的規(guī)矩將線程“排隊”疏咐,確定好“誰先消費共享資源”纤掸,而這個規(guī)矩必須滿足于業(yè)務(wù)的要求。
- 共享資源才需同步凳鬓,只有共享資源的讀寫訪問才需要同步茁肠。如果不是共享資源,那么就根本沒有同步的必要缩举。
- 只對“變量”進行同步,只有“變量”(這里的變量指的是資源存在可變性)才需要同步訪問匹颤。如果共享的資源是固定不變的仅孩,那么就相當(dāng)于“常量”,線程同時讀取常量也不需要同步印蓖。至少一個線程修改共享資源辽慕,這樣的情況下,線程之間就需要同步赦肃。
- 共享資源并非同份代碼溅蛉,多個線程訪問共享資源的代碼有可能是同一份代碼公浪,也有可能是不同的代碼;無論是否執(zhí)行同一份代碼船侧,只要這些線程的代碼訪問同一份可變的共享資源欠气,這些線程之間就需要同步。
2. synchronized關(guān)鍵字
Java提供了原生的synchronized關(guān)鍵字來修飾臨界資源镜撩,對共享資源加鎖预柒,使同一時間只能有一個線程得到共享資源,其它線程只能阻塞等待該線程釋放共享資源后袁梗,才可以重新競爭獲得共享資源的機會宜鸯。
有一個線典的例子:多個人上公廁,假設(shè)只有一個坑位遮怜,這里的坑位就是共享資源淋袖,多個人就是多個線程,同時只能有一個人競爭得到上坑的機會锯梁,上坑后即碗,這個人會把廁所門鎖上,其他人只能在外等這個人上完廁所后涝桅,打開廁所后拜姿,才能“蜂擁而上”競爭下一次上坑的機會。在這個例子里冯遂,廁所門鎖就相當(dāng)于synchronized關(guān)鍵字蕊肥,它將廁所這個共享資源加上了鎖,同時能允許一個線程進入蛤肌。
2.1 用法1 synchronized修飾實例方法
synchronized修飾實例方法壁却,鎖是加在這個方法的實例對象上的,當(dāng)多個線程訪問同一個實例對象的這個方法時裸准,只能有一個線程進入該方法展东。注意:這種場景下,一個實例對象對應(yīng)一把鎖炒俱,多個線程只有調(diào)用同一個實例對象時才有意義盐肃。
下面的代碼是synchroinzed修飾實例方法的例子:
public synchronized void addCount(){
for(int i = 0;i<50;i++){
count +=1;
}
}
2.2 用法2 synchronized修飾靜態(tài)方法
synchronized修飾靜態(tài)方法與修飾實例方法在語法上是相同的,參見下例:
public synchronized static void addCount(){
for(int i = 0;i<50;i++){
count +=1; //count必須為靜態(tài)變量
}
}
我們知道权悟,在JVM中每一個類只存在一個類對象(class object)砸王,在這種場景下,synchronized所加的鎖就在修飾的靜態(tài)方法所屬的類對象上峦阁,所有線程調(diào)用此靜態(tài)方法時谦铃,同一時間只有一個線程能獲得這把鎖,進行這個靜態(tài)方法榔昔。
2.3 用法3 synchronized修飾實例方法內(nèi)代碼塊
上面示例代碼中驹闰,共享資源其實只是count這個變量瘪菌,我們沒有必要對整個方法加鎖,這樣反而會影響整個程序的執(zhí)行效率嘹朗,這時候师妙,我們可以將synchronized關(guān)鍵字加在操作count變量的代碼塊上,實現(xiàn)更細粒度的控制骡显。如下:
public void addCount(){
for(int i = 0;i<50;i++){
synchronized(this){
count +=1;
}
}
}
synchronized修飾代碼塊時疆栏,需要指定一個“監(jiān)視對象”(monitor object),也就是要對哪個對象加鎖惫谤。上面示例代碼中使用this指向本實例對象壁顶,表示對實例對象加鎖,多個線程訪問此實例對象時溜歪,只能有一個線程進行synchronized修飾的代碼塊若专。
監(jiān)視對象不一定要是this,也可以是任何對象蝴猪,如下例所示:
public final Object monitor = new Object();//final修飾调衰,表示監(jiān)視對象不可變
public void addCount(){
for(int i = 0;i<50;i++){
synchronized(monitor){
count +=1;
}
}
}
上例中,我們用final修飾了監(jiān)視對象自阱,為什么呢嚎莉?因為monitor這個變量實際是只是指向監(jiān)視對象所在內(nèi)存中的地址,如果不修飾成final的話沛豌,當(dāng)我們修改這個monitor變量趋箩,讓它指向其他對象,比如 在synchronized代碼塊中加派,我們讓monitor指向其他對象(monitor = new OtherObject();) 那么monitor就不是原本我們指定的監(jiān)視對象了叫确,鎖也就失去了意義。
如果監(jiān)視對象不是同一個實例對象的話芍锦,則表示有多個不同的鎖竹勉,不同的線程可以進入不同的鎖限制的代碼塊。
2.4 用法4 synchronized修飾靜態(tài)方法內(nèi)代碼塊
同樣也可以使用synchronized來修飾靜態(tài)方法內(nèi)的代碼塊娄琉,此時監(jiān)視對象必須是類對象(class object)次乓。
注意,如果監(jiān)視的類對象不同孽水,則表示有多個不同的鎖檬输,不同的線程可以進入不同的鎖限制的代碼塊。
public synchronized static void addCount(){
for(int i = 0;i<50;i++){
synchronized(SynchronizedUsage.class){//SynchronizedUsage.class是類對象
count +=1; //count必須為靜態(tài)變量
}
}
}
2.5 釋放鎖的兩種情況
如果一個代碼塊被synchronized修飾了匈棘,當(dāng)一個線程獲取了對應(yīng)的鎖,并執(zhí)行該代碼塊時析命,其他線程便只能一直等待主卫,等待獲取鎖的線程釋放鎖逃默,而這里獲取鎖的線程釋放鎖只會有兩種情況:
- 獲取鎖的線程執(zhí)行完了該代碼塊,然后線程釋放對鎖的占有簇搅。
- 線程執(zhí)行發(fā)生異常完域,此時JVM會讓線程自動釋放鎖。