前言
本人主要是結(jié)合《Java多線程編程核心技術(shù)》這本書的第二章內(nèi)容,對synchronized關(guān)鍵字的知識進行梳理,其中會把比較抽象的概念通過生活上的例子進行說明,以便更好理解。在講解synchronized關(guān)鍵字的知識點之前,先來總結(jié)線程的一些基礎(chǔ)知識。
一、線程基礎(chǔ)知識
進程 · 線程 · 多線程
進程:通俗點來講,就是“Windows任務(wù)管理器”中的列表里面運行在內(nèi)存中的.exe文件。一個進程,至少包含一條線程。
線程:進程中獨立運行的子任務(wù),比如QQ.exe運行時,就有很多子任務(wù)在同時進行,顯示界面、顯示歌詞、發(fā)出聲音和播放廣告等這些都是線程完成的。程序執(zhí)行的一條路徑,一條線程肯定會被包含在一個進程中。
多線程:QQ.exe運行時,就有很多子任務(wù)在同時進行,顯示界面、顯示歌詞、發(fā)出聲音和播放廣告等這些都是線程完成的。這些不同的多條線程是異步的,而且CPU分配給每一條線程的時間,具備隨機性。
單線程與多線程:通過下載的例子說明,比如,第一個人來下載,那么我這個服務(wù)員器給你提供下載服務(wù),有可能花費30分鐘,如果是單線程的話,那么幾百人來下載,會發(fā)生這樣的情況:第一個人先來下載,等下載完成后,第二個人才能下載,等下載完成后,第三個人才能下載。而如果是多線程,是幾百條線程為每一個人提供下載服務(wù),互不干擾,這樣效率大大提升。
多線程實現(xiàn)的方式
方式一:繼承Thread
- 自定義class繼承Thread
- 重寫run方法
- 把新線程要做的事寫在run方法中
- 創(chuàng)建線程對象
- 開啟新線程,內(nèi)部會自動執(zhí)行run方法
public class Demo2_Thread {
/**
* @param args
*/
public static void main(String[] args) {
MyThread mt = new MyThread(); //4,創(chuàng)建自定義類的對象
mt.start(); //5,開啟線程
for(int i = 0; i < 3000; i++) {
System.out.println("bb");
}
}
}
class MyThread extends Thread { //1,定義類繼承Thread
public void run() { //2,重寫run方法
for(int i = 0; i < 3000; i++) { //3,將要執(zhí)行的代碼,寫在run方法中
System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
}
}
}
方式二:實現(xiàn)Runnable
- 自定義class實現(xiàn)Runnable接口
- 實現(xiàn)run方法
- 把新線程要做的事寫在run方法中
- 創(chuàng)建自定義的Runnable的子類對象
- 創(chuàng)建Thread對象,傳入Runnable
- 調(diào)用start()開啟新線程,內(nèi)部會自動調(diào)用Runnable的run()方法
public class Demo3_Runnable {
/**
* @param args
*/
public static void main(String[] args) {
MyRunnable mr = new MyRunnable(); //4,創(chuàng)建自定義類對象
//Runnable target = new MyRunnable();
Thread t = new Thread(mr); //5,將其當(dāng)作參數(shù)傳遞給Thread的構(gòu)造函數(shù)
t.start(); //6,開啟線程
for(int i = 0; i < 3000; i++) {
System.out.println("bb");
}
}
}
class MyRunnable implements Runnable { //1,自定義類實現(xiàn)Runnable接口
@Override
public void run() { //2,重寫run方法
for(int i = 0; i < 3000; i++) { //3,將要執(zhí)行的代碼,寫在run方法中
System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
}
}
}
二、synchronized 關(guān)鍵字
實例變量與線程安全
自定義線程類中的實例變量針對其他線程可以有共享與不共享之分。
不共享數(shù)據(jù):每條線程都對各自的實例變量進行數(shù)據(jù)操作,互不干擾,不會產(chǎn)生線程安全問題。
共享數(shù)據(jù):多條線程訪問同一個實例變量,比如實現(xiàn)投票功能的軟件時,多條線程可以同時處理同一個人的票數(shù)。如果是i++、i--的操作,那么一定會出現(xiàn)非線程安全問題。
非線程安全:主要是指多條線程對同一個對象中的同一個實例變量進行操作時出現(xiàn)值被更改、值不同步的情況,進而影響程序的執(zhí)行流程。
synchronized 使用場景
經(jīng)典示例:5個銷售員,每個銷售員賣出一個貨品后不可以得出相同的剩余數(shù)量,必須在每一個銷售員賣完一個貨品后其他銷售員才可以在新的剩余物品數(shù)上繼續(xù)減1操作,這時就需要多個線程之間進行同步,也就是用按順序排隊的方式進行減1操作。
public class MyThread extends Thread {
private int count = 5;
@Override
synchronized public void run() {
super.run();
count--;
System.out.println("由 " + this.currentThread().getName() + " 計算,count= " + count);
}
}
通過在run方法前加入synchronized關(guān)鍵字,使多條線程在執(zhí)行run方法時,以排隊的方式進行處理。當(dāng)一條線程調(diào)用run方法前,先判斷run方法有沒有被上鎖,如果上鎖,說明有其他線程正在調(diào)用方法,必須等其他線程對run方法調(diào)用結(jié)束后才可以執(zhí)行run方法。這樣實現(xiàn)了排隊調(diào)用run方法的目的,也達到了按順序?qū)ount變量減1的效果。synchrinized可以在任意對象及方法上枷鎖,而加鎖的這段代碼稱為“互斥區(qū)”或“臨界區(qū)”。當(dāng)一條線程想要執(zhí)行同步方法里面的代碼時,線程首先會嘗試去拿這把鎖,如果能拿到這把鎖,那么這個線程可以執(zhí)行synchronized里面的代碼。如果不能拿到這把鎖,那么這條線程就會不斷地嘗試拿這邊鎖,直到能夠拿到為止,而且是有多條線程同時去爭搶這把鎖。通俗點來講就是,一群人等著上洗手間,那么你總要等到里面的人出來,并且搶在其他人之前進到洗手間的門(拿到鎖),才可以方便吧。
synchronized 同步方法
- “非線程安全”其實會在多個線程對同一個對象中的實例變量進行并發(fā)訪問時發(fā)生,產(chǎn)生的后果就是“臟讀”,也就是取到的數(shù)據(jù)其實是被更改過的。
- “線程安全”是以獲得的實例變量的值是經(jīng)過同步處理的,不會出現(xiàn)臟讀的現(xiàn)象。
方法內(nèi)的變量為線程安全(代碼詳見class211)
- 方法中的變量不存在非線程安全問題,永遠都是線程安全的,這是因為方法內(nèi)部的變量是私有的特性造成的。
實例變量非線程安全(代碼詳見class211)
- 如果多個線程共同訪問一個對象中的實例變量,則有可能出現(xiàn)“非線程安全”問題,有可能出現(xiàn)覆蓋的情況,但是訪問同一個對象中的同步方法(在方法前使用關(guān)鍵字synchronized)時一定是線程安全的,例子如上所示,只要在public void addI(String username)方法前加關(guān)鍵字synchronized即可。
- 如果線程訪問的對象中有多個實例變量,則運行的結(jié)果有可能出現(xiàn)交叉的情況。
多個對象多個鎖(代碼詳見class213)
- 兩個線程分別訪問同一個類的兩個不同實例的相同名稱的同步方法,效果卻是以異步的方式運行的。關(guān)鍵字synchronized取得的鎖都是對象鎖,而不是把一段代碼或方法當(dāng)作鎖,哪個線程先執(zhí)行帶synchronized關(guān)鍵字的方法,哪個線程就持有該方法所屬對象的鎖Lock,那么其他線程只能呈等待狀態(tài),前提是多個線程訪問的是同一個對象。但如果多個線程訪問多個對象,則JVM會創(chuàng)建多個鎖,上面的示例就是創(chuàng)建了2個HasSelfPrivateNum.java類的對象所以產(chǎn)生2個鎖。同步的單詞為synchronized,異步的單詞為asynchronized。
synchronized 方法與鎖對象(代碼詳見class214)
- 調(diào)用用關(guān)鍵字synchronized聲明的方法一定是排隊運行的,另外需要牢牢記住“共享”這兩個字,只有共享資源的讀寫訪問才需要同步化,如果不是共享資源,那么根本就沒有同步的必要。
- 兩個線程A、B訪問同一個對象object的兩個不同的方法(一個同步方法,一個普通方法),雖然A線程持有了object對象的Lock鎖,但是B線程完全可以異步調(diào)用非synchronized類型的方法。
- 兩個線程A、B訪問同一個對象object的兩個同步的方法,A線程持有了object對象的Lock鎖,B線程如果在這時調(diào)用object對象中的synchronized類型的方法則需要等待,也就是同步。
臟讀(代碼詳見class215)
- 多個線程調(diào)用同一個方法時,為了避免數(shù)據(jù)出現(xiàn)交叉的情況,使用synchronized關(guān)鍵字來進行同步,雖然在賦值時進行了同步,但是在取值時有可能出現(xiàn)在讀取實例變量時,此值已經(jīng)被其他線程更改過了,這種情況就是臟讀。
- 通過class215案例不僅要知道臟讀是通過synchronized關(guān)鍵字解決的,還要知道如下內(nèi)容:
- 當(dāng)A線程調(diào)用anyObject對象加入synchronized關(guān)鍵字的XXX方法時,A線程就獲得了XXX方法鎖,更準(zhǔn)確地講,是獲得了對象的鎖,所以其他線程必須等A線程執(zhí)行完畢才可以調(diào)用XXX方法,但B線程可以任意調(diào)用其他的非synchronized同步方法。
- 當(dāng)A線程調(diào)用anyObject對象加入synchronized關(guān)鍵字的XXX方法時,A線程就獲得了方法所在對象的鎖,所以其他線程必須等A線程執(zhí)行完畢才可以調(diào)用XXX方法,而B線程如果調(diào)用聲明了synchronized關(guān)鍵字的非XXX方法時,必須等A線程將XXX方法執(zhí)行完,也就是釋放對象鎖后才可以調(diào)用。這時A線程已經(jīng)執(zhí)行了一個完整的任務(wù),也就是說username和password這兩個實例變量已經(jīng)同時被賦值,不存在臟讀的基本環(huán)境。臟讀一定會出現(xiàn)在操作實例變量的情況下,這就是不同線程“爭搶”實例變量的結(jié)果。
synchronized鎖重入(代碼詳見class216)
- synchronized擁有鎖重入的功能,也就是在使用synchronized時,當(dāng)一個線程得到一個對象鎖后,再次請求此對象鎖時是可以再次得到該對象的鎖的,這也證明了在一個synchronized方法/塊的內(nèi)部調(diào)用本類的其他synchronized方法/塊時,是永遠可以得到鎖的。(例子1)
- “可重入鎖”的概念是:自己可以再次獲取自己的內(nèi)部鎖,比如有一條線程獲取了某個對象的鎖,此時這個對象鎖還沒有釋放,當(dāng)其再次想要獲取這個對象的鎖的時候還是可以獲取的,如果不可鎖重入的話,就會造成死鎖。可重入鎖也支持在父子類繼承的環(huán)境中。(例子2)此實驗說明,當(dāng)存在父子類繼承關(guān)系時,子類是完全可以通過“可重入鎖”調(diào)用父類的同步方法的。
出現(xiàn)異常,鎖自動釋放(代碼詳見class217)
- 當(dāng)一個線程執(zhí)行的的代碼出現(xiàn)異常時,其所持有的鎖會自動釋放。
同步不具有繼承性(代碼詳見class218)
- 同步不可以繼承。從class218示例可以可看出,同步不能繼承,所以還得在子類的方法中添加synchronized關(guān)鍵字。
synchronized同步語句塊
- 用關(guān)鍵字synchronized聲明方法在某些情況下是有弊端的,比如A線程調(diào)用同步方法執(zhí)行一個長時間的任務(wù),那么B線程則必須等待比較長的時間,這樣的情況下可以使用synchronized同步語句塊來解決。
synchronized方法的弊端(代碼詳見class221)
synchronized同步代碼塊的使用(代碼詳見class222)
- 當(dāng)兩個并發(fā)線程訪問同一個對象object中的synchronized(this)同步代碼塊時,一段時間內(nèi)只能有一個線程被執(zhí)行,另一個線程必須等待當(dāng)前線程執(zhí)行完這個代碼塊以后才能執(zhí)行該代碼塊。
用同步代碼塊解決同步方法的弊端(代碼詳見class223)
- 當(dāng)一個線程訪問object的一個synchronized同步代碼塊時,另一個線程仍然可以訪問該object對象中的非synchronized(this)同步代碼塊。
一半異步,一半同步(代碼詳見class224)
- 不在synchronized塊中就是異步執(zhí)行,在synchronized塊中就是同步執(zhí)行。
synchronized代碼塊間的同步性(代碼詳見class225)
- 在使用同步synchronized(this)代碼塊時需要注意的是,當(dāng)一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對同一個object中所有其他synchronized(this)同步代碼塊的訪問將被阻塞,這說明synchronized使用的“對象監(jiān)視器”是一個。
驗證同步synchronized(this)代碼塊是鎖定當(dāng)前對象的(代碼詳見class226)
- 和synchronized方法一樣,synchronized(this)代碼塊也是鎖定當(dāng)前對象的。
將任意對象作為對象監(jiān)視器(代碼詳見class227)
- 多個線程調(diào)用一個對象中的不同名稱的synchronized同步方法或synchronized(this)同步代碼塊時,調(diào)用的效果就是按順序執(zhí)行,也就是同步的,阻塞的。這說明synchronized同步方法或synchronized(this)同步代碼塊分別有兩種作用:
- synchronized同步方法
- 對其他synchronized同步方法或synchronized(this)同步代碼塊調(diào)用呈阻塞狀態(tài)。
- 同一時間只有一個線程可以執(zhí)行synchronized同步方法中的代碼
- synchronized(this)同步代碼塊
- 對其他synchronized同步方法或synchronized(this)同步代碼塊調(diào)用呈阻塞狀態(tài)。
- 同一時間只有一個線程可以執(zhí)行synchronized(this)同步代碼塊中的代碼。
- synchronized同步方法
- 使用synchronized(this)格式來同步代碼塊,其實Java還支持對“任意對象”作為“對象監(jiān)視器”來實現(xiàn)同步的功能。這個“任意對象”大多數(shù)是實例變量及方法的參數(shù),使用格式為synchronized(非this對象)。
- 根據(jù)前面對synchronized(this)同步代碼塊的作用總結(jié)可知,synchronized(非this對象)格式的作用只有1種:synchronized(非this對象 X)同步代碼塊。
- 在多個線程持有“對象監(jiān)視器”為同一個對象的前提下,同一時間只有一個線程可以執(zhí)行synchronized(非this對象 X)同步代碼塊中的代碼。
- 當(dāng)持有“對象監(jiān)視器”為同一個對象的前提下,同一時間只有一個線程可以執(zhí)行synchronized(非this 對象 X)同步代碼塊中的代碼。
- 鎖非this對象具有一定的優(yōu)點:如果在一個類中有很多個synchronized方法,這時雖然能實現(xiàn)同步,但會受到阻塞,所以影響運行效率,但如果使用同步代碼塊鎖非this對象,則synchronized(非 this)代碼塊中的程序與同步方法是異步的,不與其他鎖this同步方法爭搶this鎖,則可大大提高運行效率。
- 使用“synchronized(非this對象X)同步代碼塊”格式時,持有不同的對象監(jiān)視器是異步的效果。同步代碼塊放在非同步synchronized方法中進行聲明,并不能保證調(diào)用方法的線程的執(zhí)行同步/順序性,也就是線程調(diào)用方法的順序是無序的,雖然在同步塊中執(zhí)行的順序是同步的,這樣極易出現(xiàn)“臟讀”問題。(代碼詳見class227_2)
- 多個線程調(diào)用同一個方法是隨機的。(代碼詳見class227_3)
2.4.8 細化驗證3個結(jié)論(代碼詳見class228)
- “synchronized(非 this 對象 X)”格式的寫法是將X對象本身作為“對象監(jiān)視器”,這樣就可以得出3個結(jié)論:
- 當(dāng)多個線程同時執(zhí)行synchronized(X){}同步代碼塊時呈同步效果。
- 當(dāng)其他線程執(zhí)行X對象中synchronized同步方法時呈同步效果。
- 當(dāng)其他線程執(zhí)行X對象方法里面的synchronized(this)代碼塊時呈同步效果。
靜態(tài)同步synchronized方法與synchronized(class)代碼塊
- synchronized加到static靜態(tài)方法上是給Class類上鎖,而它加到非static靜態(tài)方法上是給對象上鎖。示例運行結(jié)果異步的原因是持有不同的鎖,一個是對象鎖,另外一個是Class鎖,而Class鎖可以對類的所有對象實例起作用。
- 同步synchronized(class)代碼塊的作用和synchronized static 方法的作用一樣。
數(shù)據(jù)類型String的常量池特性
- 在JVM中具有String常量池緩存的功能,同步synchronized代碼塊都不使用String作為鎖對象,而改用其他,比如new Object()實例化一個Object對象,但它并不放入緩存中。
同步synchronized方法無限等待與解決
同步方法容易造成死循環(huán),這個問題可以使用同步代碼塊來解決。
多線程的死鎖
- Java多線程死鎖是一個經(jīng)典的多線程問題,因為不同的線程都在等待根本不可能被釋放的鎖,從而導(dǎo)致所有的任務(wù)都無法繼續(xù)完成,在多線程技術(shù)中,“死鎖”是必須避免的,因為這會造成線程的“假死”。
- 死鎖是程序設(shè)計的Bug,在設(shè)計程序時要避免雙方互相持有對方的鎖的情況,死鎖與synchronized嵌套的代碼結(jié)構(gòu)沒有關(guān)系,只要互相等待對方釋放鎖就有可能出現(xiàn)死鎖。
內(nèi)置類與靜態(tài)內(nèi)置類
內(nèi)置類與同步:實驗1
- 在內(nèi)置類中有兩個同步方法,但使用的卻是不同的鎖,打印的結(jié)果是異步的。
內(nèi)置類與同步:實驗2
- 同步代碼塊synchronized(class2)對class2上鎖后,其他線程只能以同步的方式調(diào)用class2中的靜態(tài)同步方法。
鎖對象的改變
- 在將任何數(shù)據(jù)類型作為同步鎖時,需要注意的是,是否有多個線程同時持有鎖對象,如果同時持有相同的鎖對象,則這些線程之間就是同步的,如果分別獲得鎖對象,這些線程之間就是異步的。只要對象不變,即使對象的屬性被改變,運行結(jié)果還是同步的。
三、volatile 關(guān)鍵字
- 關(guān)鍵字 volatile的主要作用是使變量在多個線程間可見。
volatile 關(guān)鍵字與死循環(huán)
- 如果不是在多繼承的情況下,使用繼承Thread類和實現(xiàn)Runnable接口在取得程序運行的結(jié)果上并沒有太大的區(qū)別,如果一旦出現(xiàn)“多繼承”的情況,則實現(xiàn)Runnable接口的方式來處理多線程的問題上就很有必要。
解決同步死循環(huán)
- 關(guān)鍵字Volatile的作用是強制從公共堆棧中取得變量的值,而不是從線程私有數(shù)據(jù)棧中取得變量的值。
解決異步死循環(huán)
- 這個問題其實就是私有堆棧中的值和公共堆棧中的值不同步造成的,解決這樣的問題就要使用volatile關(guān)鍵字,它主要的作用就是當(dāng)線程訪問xxxx這個變量時,強制性從公共堆棧中進行取值。
- volatile關(guān)鍵字最致命的缺點是不支持原子性。
- synchronized 和 volatile 的比較:
- volatitle是線程同步的輕量級實現(xiàn),所有volatile性能肯定比synchronized要好,并且volatile只能修飾于變量,而synchronized可以修飾方法以及代碼塊,隨著JDK新版本的發(fā)布,synchronized在執(zhí)行效率上得到很大的提升,在開發(fā)中使用synchronized的比率還是比較大的。
- 多線程訪問volatile不會發(fā)生阻塞,而synchronized會出現(xiàn)阻塞。
- volatile能保證數(shù)據(jù)的可見性,但不能保證原子性,而synchronized可以保證原子性,也可以間接保證可見性,因為它會將私有內(nèi)存和公共內(nèi)存中的數(shù)據(jù)做同步。
- volatile解決的是變量在多個線程之間的可見性,而synchronized解決的是多個線程之間訪問資源的同步性。
- 線程安全包含原子性和可見性兩個方面,Java的同步機制都是圍繞這個兩個方面來確保線程安全的。
3.4 volatile 非原子的特性
- volatile雖然增加了實例變量在多個線程之間的可見性,但它卻不具備同步性,那么也就不具備原子性。
- volatile主要使用的場合是在多個線程中可以感知實例變量被更改了,并且可以獲得最新的值的使用,也就是多線程讀取共享變量時可以獲得最新值的使用。
- volatile提示線程每次從共享內(nèi)存中讀取變量,而不是從私有內(nèi)存中讀取,這樣就保證了同步數(shù)據(jù)的可見性,但是如果修改實例變量中的數(shù)據(jù),比如i++,也就是i=i+1,則這樣的操作其實并不是一個原子操作,也就是非線程安全。表達式i++的操作步驟分解如下:
- 從內(nèi)存中取出i的值
- 計算i的值
- 將i的值寫到內(nèi)存中
假如在第2步計算值的時候,另外一個線程也修改i的值,那么這個時候就會出現(xiàn)“臟讀”,解決的辦法其實就是使用synchronized,所以說volatile本身并不處理數(shù)據(jù)的原子性,而是強制對數(shù)據(jù)的讀寫及時影響到主內(nèi)存。
- 用圖來演示一下使用volatile時出現(xiàn)非線程安全的原因。變量在內(nèi)存中工作的過程如下圖所示:
- read階段和load階段:從主存復(fù)制變量到當(dāng)前線程工作內(nèi)存。
- use和assign階段:執(zhí)行代碼,改變共享變量值。
- store和write階段:用工作內(nèi)存數(shù)據(jù)刷新主存對應(yīng)變量的值。
- 在多線程環(huán)境中,use和assign是多次出現(xiàn)的,但這一操作并不是原子性,也就是在read和load之后,如果主內(nèi)存count變量發(fā)生修改之后,線程工作內(nèi)存中的值由于已經(jīng)加載,不會產(chǎn)生對應(yīng)的變化,也就是私有內(nèi)存和公共內(nèi)存中的變量不同步,所有計算出來的結(jié)果會和預(yù)期不一樣,也就是出現(xiàn)了非線程安全問題。
- 對于用volatile修飾的變量,JVM虛擬機只是保證從主內(nèi)存加載到線程工作內(nèi)存的值是最新的,例如線程1和線程2在進行read和load的操作中,發(fā)現(xiàn)主內(nèi)存中count的值都是5,那么都會加載這個最新的值。也就是說,volatile關(guān)鍵字解決的是變量讀時的可見性問題,但無法保證原子性,對于多個線程訪問同一個實例變量還是需要加鎖同步的。
使用原子類進行i++操作
- 除了在i++操作時使用synchronized關(guān)鍵字實現(xiàn)同步外,還可以使用AtomicInteger原子類進行實現(xiàn)。
- 原子操作是不能分割的整體,沒有其他線程能夠中斷或檢查正在原子操作中的變量。一個原子(atomic)類型就是一個原子操作可用的類型,它可以在沒有鎖的情況下做到線程安全(thread-safe)。
原子類也并不完全安全
- 原子類在具有有邏輯性的情況下輸出結(jié)果也具有隨機性。
synchronized 代碼塊有volatile同步的功能
- synchronized 可以使多個線程訪問同一個資源具有同步性,而且它還具有將線程工作內(nèi)存中的私有變量與公共內(nèi)存中的變量同步的功能。
- synchronized 可以保證在同一時刻,只有一個線程可以執(zhí)行某一個方法或某一個代碼塊。它包含兩個特征:互斥性和可見性。synchronized不僅可以解決一個線程看到對象處于不一致的狀態(tài),還可以保證進入同步方法或者同步代碼塊的每個線程都看到由同一個鎖保護之前所有的修改效果。
四、作業(yè)
第一周:理解synchronized的含義、明確synchronized關(guān)鍵字修飾普通方法、靜態(tài)方法和代碼塊時鎖對象的差異。
問題一:有如下一個類A
class A {
public synchronized void a() {
}
public synchronized void b() {
}
}
然后創(chuàng)建兩個對象
A a1 = new A();
A a2 = new A();
然后在兩個線程中并發(fā)訪問如下代碼
Thread1 Thread2
a1.a(); a2.a();
請問二者能否構(gòu)成線程同步?
問題二:如果A的定義是下面這種呢?
class A {
public static synchronized void a() {
}
public static synchronized void b() {
}
}
解答一:兩個線程thread1、thread2訪問同一個對象A的兩個同步方法,thread1線程持有了A對象的Lock鎖,thread2線程如果在這時調(diào)用A對象中的synchronized類型的方法則需要等待,也就是同步。synchronized取得的鎖是對象鎖,而不是把一段代碼或方法當(dāng)作鎖,哪個線程先執(zhí)行帶synchronized關(guān)鍵字的方法,哪個線程就持有該方法所屬對象的鎖Lock,那么其他線程只能呈等待狀態(tài),前提是多個線程訪問的是同一個對象,如果多個線程訪問多個對象,則JVM會創(chuàng)建多個鎖,上面的示例雖然是兩條線程并發(fā)訪問了A對象里面的同步方法a,但是因為創(chuàng)建了2個new A()對象,那么就會產(chǎn)生2個鎖,所以thread1和thread2這個兩條線程是異步的方式運行的。值得一提的是,和synchronized方法一樣,synchronized(this)代碼塊也是鎖定當(dāng)前對象的。
解答二:synchronized加到static靜態(tài)方法上是給Class類上鎖的,而它加到非static靜態(tài)方法上是給對象上鎖的。Class鎖可以對類的所有對象實例起作用,所以雖然創(chuàng)建了兩個不同的A對象a1和a2,但是兩個線程thread1和thread2是同步的方式運行的。值得一提的是,同步synchronized(Class)代碼塊的作用和synchronized static 方法的作用是一樣。