Java 并發(fā):內(nèi)置鎖 Synchronized_第1頁(yè)
Java 并發(fā):內(nèi)置鎖 Synchronized_第2頁(yè)
Java 并發(fā):內(nèi)置鎖 Synchronized_第3頁(yè)
Java 并發(fā):內(nèi)置鎖 Synchronized_第4頁(yè)
Java 并發(fā):內(nèi)置鎖 Synchronized_第5頁(yè)
已閱讀5頁(yè),還剩10頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、Java 并發(fā):內(nèi)置鎖 Synchronized一. 線程安全問(wèn)題在單線程中不會(huì)出現(xiàn)線程安全問(wèn)題,而在多線程編程中,有可能會(huì)出現(xiàn)同時(shí)訪問(wèn)同一個(gè) 共享、可變資源 的情況,這種資源可以是:一個(gè)變量、一個(gè)對(duì)象、一個(gè)文件等。特別注意兩點(diǎn),共享: 意味著該資源可以由多個(gè)線程同時(shí)訪問(wèn);可變: 意味著該資源可以在其生命周期內(nèi)被修改。所以,當(dāng)多個(gè)線程同時(shí)訪問(wèn)這種資源的時(shí)候,就會(huì)存在一個(gè)問(wèn)題:由于每個(gè)線程執(zhí)行的過(guò)程是不可控的,所以需要采用同步機(jī)制來(lái)協(xié)同對(duì)對(duì)象可變狀態(tài)的訪問(wèn)。 舉個(gè) 數(shù)據(jù)臟讀 的例子:/資源類class PublicVar public String username = A; public St

2、ring password = AA; /同步實(shí)例方法 public synchronized void setValue(String username, String password) try this.username = username; Thread.sleep(5000); this.password = password; System.out.println(method=setValue +t + threadName= + Thread.currentThread().getName() + t + username= + username + , password=

3、+ password); catch (InterruptedException e) e.printStackTrace(); /非同步實(shí)例方法 public void getValue() System.out.println(method=getValue + t + threadName= + Thread.currentThread().getName()+ t + username= + username + , password= + password); /線程類class ThreadA extends Thread private PublicVar publicVar;

4、public ThreadA(PublicVar publicVar) super(); this.publicVar = publicVar; Override public void run() super.run(); publicVar.setValue(B, BB); /測(cè)試類public class Test public static void main(String args) try /臨界資源 PublicVar publicVarRef = new PublicVar(); /創(chuàng)建并啟動(dòng)線程 ThreadA thread = new ThreadA(publicVarRe

5、f); thread.start(); Thread.sleep(200);/ 打印結(jié)果受此值大小影響 /在主線程中調(diào)用 publicVarRef.getValue(); catch (InterruptedException e) e.printStackTrace(); /* Output ( 數(shù)據(jù)交叉 ): method=getValue threadName=main username=B, password=AA method=setValue threadName=Thread-0 username=B, password=BB */:由程序輸出可知,雖然在寫操作進(jìn)行了同步,但在讀

6、操作上仍然有可能出現(xiàn)一些意想不到的情況,例如上面所示的 臟讀。發(fā)生 臟讀 的情況是在執(zhí)行讀操作時(shí),相應(yīng)的數(shù)據(jù)已被其他線程 部分修改 過(guò),導(dǎo)致 數(shù)據(jù)交叉 的現(xiàn)象產(chǎn)生。這其實(shí)就是一個(gè)線程安全問(wèn)題,即多個(gè)線程同時(shí)訪問(wèn)一個(gè)資源時(shí),會(huì)導(dǎo)致程序運(yùn)行結(jié)果并不是想看到的結(jié)果。這里面,這個(gè)資源被稱為:臨界資源。也就是說(shuō),當(dāng)多個(gè)線程同時(shí)訪問(wèn)臨界資源(一個(gè)對(duì)象,對(duì)象中的屬性,一個(gè)文件,一個(gè)數(shù)據(jù)庫(kù)等)時(shí),就可能會(huì)產(chǎn)生線程安全問(wèn)題。不過(guò),當(dāng)多個(gè)線程執(zhí)行一個(gè)方法時(shí),該方法內(nèi)部的局部變量并不是臨界資源,因?yàn)檫@些局部變量是在每個(gè)線程的私有棧中,因此不具有共享性,不會(huì)導(dǎo)致線程安全問(wèn)題。二. 如何解決線程安全問(wèn)題實(shí)際上,所有的并

7、發(fā)模式在解決線程安全問(wèn)題時(shí),采用的方案都是 序列化訪問(wèn)臨界資源 。即在同一時(shí)刻,只能有一個(gè)線程訪問(wèn)臨界資源,也稱作 同步互斥訪問(wèn)。換句話說(shuō),就是在訪問(wèn)臨界資源的代碼前面加上一個(gè)鎖,當(dāng)訪問(wèn)完臨界資源后釋放鎖,讓其他線程繼續(xù)訪問(wèn)。在 Java 中,提供了兩種方式來(lái)實(shí)現(xiàn)同步互斥訪問(wèn):synchronized 和 Lock。本文主要講述 synchronized 的使用方法,Lock 的使用方法在下一篇博文中講述。三. synchronized 同步方法或者同步塊在了解 synchronized 關(guān)鍵字的使用方法之前,我們先來(lái)看一個(gè)概念:互斥鎖,即 能到達(dá)到互斥訪問(wèn)目的的鎖。舉個(gè)簡(jiǎn)單的例子,如果對(duì)臨界

8、資源加上互斥鎖,當(dāng)一個(gè)線程在訪問(wèn)該臨界資源時(shí),其他線程便只能等待。在 Java 中,可以使用 synchronized 關(guān)鍵字來(lái)標(biāo)記一個(gè)方法或者代碼塊,當(dāng)某個(gè)線程調(diào)用該對(duì)象的synchronized方法或者訪問(wèn)synchronized代碼塊時(shí),這個(gè)線程便獲得了該對(duì)象的鎖,其他線程暫時(shí)無(wú)法訪問(wèn)這個(gè)方法,只有等待這個(gè)方法執(zhí)行完畢或者代碼塊執(zhí)行完畢,這個(gè)線程才會(huì)釋放該對(duì)象的鎖,其他線程才能執(zhí)行這個(gè)方法或者代碼塊。下面這段代碼中兩個(gè)線程分別調(diào)用insertData對(duì)象插入數(shù)據(jù):1) synchronized方法public class Test public static void main(Stri

9、ng args) final InsertData insertData = new InsertData(); / 啟動(dòng)線程 1 new Thread() public void run() insertData.insert(Thread.currentThread(); ; .start(); / 啟動(dòng)線程 2 new Thread() public void run() insertData.insert(Thread.currentThread(); ; .start(); class InsertData / 共享、可變資源 private ArrayList arrayList

10、= new ArrayList(); /對(duì)共享可變資源的訪問(wèn) public void insert(Thread thread) for(int i=0;i5;i+) System.out.println(thread.getName()+在插入數(shù)據(jù)+i); arrayList.add(i); /* Output: Thread-0在插入數(shù)據(jù)0 Thread-1在插入數(shù)據(jù)0 Thread-0在插入數(shù)據(jù)1 Thread-0在插入數(shù)據(jù)2 Thread-1在插入數(shù)據(jù)1 Thread-1在插入數(shù)據(jù)2 */:根據(jù)運(yùn)行結(jié)果就可以看出,這兩個(gè)線程在同時(shí)執(zhí)行insert()方法。而如果在insert()方法前

11、面加上關(guān)鍵字synchronized 的話,運(yùn)行結(jié)果為:class InsertData private ArrayList arrayList = new ArrayList(); public synchronized void insert(Thread thread) for(int i=0;i5;i+) System.out.println(thread.getName()+在插入數(shù)據(jù)+i); arrayList.add(i); /* Output: Thread-0在插入數(shù)據(jù)0 Thread-0在插入數(shù)據(jù)1 Thread-0在插入數(shù)據(jù)2 Thread-1在插入數(shù)據(jù)0 Thread-

12、1在插入數(shù)據(jù)1 Thread-1在插入數(shù)據(jù)2 */:從以上輸出結(jié)果可以看出,Thread-1 插入數(shù)據(jù)是等 Thread-0 插入完數(shù)據(jù)之后才進(jìn)行的。說(shuō)明 Thread-0 和 Thread-1 是順序執(zhí)行 insert() 方法的。這就是 synchronized 關(guān)鍵字對(duì)方法的作用。不過(guò)需要注意以下三點(diǎn):1)當(dāng)一個(gè)線程正在訪問(wèn)一個(gè)對(duì)象的 synchronized 方法,那么其他線程不能訪問(wèn)該對(duì)象的其他 synchronized 方法。這個(gè)原因很簡(jiǎn)單,因?yàn)橐粋€(gè)對(duì)象只有一把鎖,當(dāng)一個(gè)線程獲取了該對(duì)象的鎖之后,其他線程無(wú)法獲取該對(duì)象的鎖,所以無(wú)法訪問(wèn)該對(duì)象的其他synchronized方法。2)

13、當(dāng)一個(gè)線程正在訪問(wèn)一個(gè)對(duì)象的 synchronized 方法,那么其他線程能訪問(wèn)該對(duì)象的非 synchronized 方法。這個(gè)原因很簡(jiǎn)單,訪問(wèn)非 synchronized 方法不需要獲得該對(duì)象的鎖,假如一個(gè)方法沒(méi)用 synchronized 關(guān)鍵字修飾,說(shuō)明它不會(huì)使用到臨界資源,那么其他線程是可以訪問(wèn)這個(gè)方法的,3)如果一個(gè)線程 A 需要訪問(wèn)對(duì)象 object1 的 synchronized 方法 fun1,另外一個(gè)線程 B 需要訪問(wèn)對(duì)象 object2 的 synchronized 方法 fun1,即使 object1 和 object2 是同一類型),也不會(huì)產(chǎn)生線程安全問(wèn)題,因?yàn)樗麄冊(cè)L問(wèn)

14、的是不同的對(duì)象,所以不存在互斥問(wèn)題。2) synchronized 同步塊synchronized 代碼塊類似于以下這種形式:synchronized (lock) /訪問(wèn)共享可變資源 .當(dāng)在某個(gè)線程中執(zhí)行這段代碼塊,該線程會(huì)獲取對(duì)象lock的鎖,從而使得其他線程無(wú)法同時(shí)訪問(wèn)該代碼塊。其中,lock 可以是 this,代表獲取當(dāng)前對(duì)象的鎖,也可以是類中的一個(gè)屬性,代表獲取該屬性的鎖。特別地, 實(shí)例同步方法 與 synchronized(this)同步塊 是互斥的,因?yàn)樗鼈冩i的是同一個(gè)對(duì)象。但與 synchronized(非this)同步塊 是異步的,因?yàn)樗鼈冩i的是不同對(duì)象。比如上面的inser

15、t()方法可以改成以下兩種形式:/ this 監(jiān)視器class InsertData private ArrayList arrayList = new ArrayList(); public void insert(Thread thread) synchronized (this) for(int i=0;i100;i+) System.out.println(thread.getName()+在插入數(shù)據(jù)+i); arrayList.add(i); / 對(duì)象監(jiān)視器class InsertData private ArrayList arrayList = new ArrayList();

16、private Object object = new Object(); public void insert(Thread thread) synchronized (object) for(int i=0;i100;i+) System.out.println(thread.getName()+在插入數(shù)據(jù)+i); arrayList.add(i); 從上面代碼可以看出,synchronized代碼塊 比 synchronized方法 的粒度更細(xì)一些,使用起來(lái)也靈活得多。因?yàn)橐苍S一個(gè)方法中只有一部分代碼只需要同步,如果此時(shí)對(duì)整個(gè)方法用synchronized進(jìn)行同步,會(huì)影響程序執(zhí)行效率。而

17、使用synchronized代碼塊就可以避免這個(gè)問(wèn)題,synchronized代碼塊可以實(shí)現(xiàn)只對(duì)需要同步的地方進(jìn)行同步。3) class 對(duì)象鎖特別地,每個(gè)類也會(huì)有一個(gè)鎖,靜態(tài)的 synchronized方法 就是以Class對(duì)象作為鎖。另外,它可以用來(lái)控制對(duì) static 數(shù)據(jù)成員 (static 數(shù)據(jù)成員不專屬于任何一個(gè)對(duì)象,是類成員) 的并發(fā)訪問(wèn)。并且,如果一個(gè)線程執(zhí)行一個(gè)對(duì)象的非static synchronized 方法,另外一個(gè)線程需要執(zhí)行這個(gè)對(duì)象所屬類的 static synchronized 方法,也不會(huì)發(fā)生互斥現(xiàn)象。因?yàn)樵L問(wèn) static synchronized 方法占用的

18、是類鎖,而訪問(wèn)非 static synchronized 方法占用的是對(duì)象鎖,所以不存在互斥現(xiàn)象。例如,public class Test public static void main(String args) final InsertData insertData = new InsertData(); new Thread() Override public void run() insertData.insert(); .start(); new Thread() Override public void run() insertData.insert1(); .start(); cl

19、ass InsertData / 非 static synchronized 方法 public synchronized void insert() System.out.println(執(zhí)行insert); try Thread.sleep(5000); catch (InterruptedException e) e.printStackTrace(); System.out.println(執(zhí)行insert完畢); / static synchronized 方法 public synchronized static void insert1() System.out.println(

20、執(zhí)行insert1); System.out.println(執(zhí)行insert1完畢); /* Output: 執(zhí)行insert 執(zhí)行insert1 執(zhí)行insert1完畢 執(zhí)行insert完畢 */:根據(jù)執(zhí)行結(jié)果,我們可以看到第一個(gè)線程里面執(zhí)行的是insert方法,不會(huì)導(dǎo)致第二個(gè)線程執(zhí)行insert1方法發(fā)生阻塞現(xiàn)象。下面,我們看一下 synchronized 關(guān)鍵字到底做了什么事情,我們來(lái)反編譯它的字節(jié)碼看一下,下面這段代碼反編譯后的字節(jié)碼為:public class InsertData private Object object = new Object(); public void

21、insert(Thread thread) synchronized (object) public synchronized void insert1(Thread thread) public void insert2(Thread thread)從反編譯獲得的字節(jié)碼可以看出,synchronized 代碼塊實(shí)際上多了 monitorenter 和 monitorexit 兩條指令。 monitorenter指令執(zhí)行時(shí)會(huì)讓對(duì)象的鎖計(jì)數(shù)加1,而monitorexit指令執(zhí)行時(shí)會(huì)讓對(duì)象的鎖計(jì)數(shù)減1,其實(shí)這個(gè)與操作系統(tǒng)里面的PV操作很像,操作系統(tǒng)里面的PV操作就是用來(lái)控制多個(gè)進(jìn)程對(duì)臨界資源的訪問(wèn)

22、。對(duì)于synchronized方法,執(zhí)行中的線程識(shí)別該方法的 method_info 結(jié)構(gòu)是否有 ACC_SYNCHRONIZED 標(biāo)記設(shè)置,然后它自動(dòng)獲取對(duì)象的鎖,調(diào)用方法,最后釋放鎖。如果有異常發(fā)生,線程自動(dòng)釋放鎖。有一點(diǎn)要注意:對(duì)于 synchronized方法 或者 synchronized代碼塊,當(dāng)出現(xiàn)異常時(shí),JVM會(huì)自動(dòng)釋放當(dāng)前線程占用的鎖,因此不會(huì)由于異常導(dǎo)致出現(xiàn)死鎖現(xiàn)象。四. 可重入性一般地,當(dāng)某個(gè)線程請(qǐng)求一個(gè)由其他線程持有的鎖時(shí),發(fā)出請(qǐng)求的線程就會(huì)阻塞。然而,由于 Java 的內(nèi)置鎖是可重入的,因此如果某個(gè)線程試圖獲得一個(gè)已經(jīng)由它自己持有的鎖時(shí),那么這個(gè)請(qǐng)求就會(huì)成功??芍厝腈i

23、最大的作用是避免死鎖。例如:public class Test implements Runnable / 可重入鎖測(cè)試 public synchronized void get() System.out.println(Thread.currentThread().getName(); set(); public synchronized void set() System.out.println(Thread.currentThread().getName(); Override public void run() get(); public static void main(String

24、 args) Test test = new Test(); new Thread(test,Thread-0).start(); new Thread(test,Thread-1).start(); new Thread(test,Thread-2).start(); /* Output: Thread-1 Thread-1 Thread-2 Thread-2 Thread-0 Thread-0 */:五. 注意事項(xiàng)1). 內(nèi)置鎖與字符串常量由于字符串常量池的原因,在大多數(shù)情況下,同步synchronized代碼塊 都不使用 String 作為鎖對(duì)象,而改用其他,比如 new Object(

25、) 實(shí)例化一個(gè) Object 對(duì)象,因?yàn)樗⒉粫?huì)被放入緩存中。看下面的例子:/資源類class Service public void print(String stringParam) try synchronized (stringParam) while (rue) System.out.println(Thread.currentThread().getName(); Thread.sleep(1000); catch (InterruptedException e) e.printStackTrace(); /線程Aclass ThreadA extends Thread priva

26、te Service service; public ThreadA(Service service) super(); this.service = service; Override public void run() service.print(AA); /線程Bclass ThreadB extends Thread private Service service; public ThreadB(Service service) super(); this.service = service; Override public void run() service.print(AA);

27、/測(cè)試public class Run public static void main(String args) /臨界資源 Service service = new Service(); /創(chuàng)建并啟動(dòng)線程A ThreadA a = new ThreadA(service); a.setName(A); a.start(); /創(chuàng)建并啟動(dòng)線程B ThreadB b = new ThreadB(service); b.setName(B); b.start(); /* Output (死鎖): A A A A . */:出現(xiàn)上述結(jié)果就是因?yàn)?String 類型的參數(shù)都是 “AA”,兩個(gè)線程持有

28、相同的鎖,所以 線程B 始終得不到執(zhí)行,造成死鎖。進(jìn)一步地,所謂死鎖是指: 不同的線程都在等待根本不可能被釋放的鎖,從而導(dǎo)致所有的任務(wù)都無(wú)法繼續(xù)完成。b). 鎖的是對(duì)象而非引用在將任何數(shù)據(jù)類型作為同步鎖時(shí),需要注意的是,是否有多個(gè)線程將同時(shí)去競(jìng)爭(zhēng)該鎖對(duì)象: 1).若它們將同時(shí)競(jìng)爭(zhēng)同一把鎖,則這些線程之間就是同步的; 2).否則,這些線程之間就是異步的??聪旅娴睦樱?資源類class MyService private String lock = 123; public void testMethod() try synchronized (ock) System.out.println(Th

29、read.currentThread().getName() + begin + System.currentTimeMillis(); lock = 456; Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + end + System.currentTimeMillis(); catch (InterruptedException e) e.printStackTrace(); /線程Bclass ThreadB extends Thread private MyService service;

30、 public ThreadB(MyService service) super(); this.service = service; Override public void run() service.testMethod(); /線程Aclass ThreadA extends Thread private MyService service; public ThreadA(MyService service) super(); this.service = service; Override public void run() service.testMethod(); /測(cè)試publ

31、ic class Run1 public static void main(String args) throws InterruptedException /臨界資源 MyService service = new MyService(); /線程A ThreadA a = new ThreadA(service); a.setNme(A); /線程B ThreadB b = new ThreadB(service); b.setName(B); a.start(); Thread.sleep(50);/ 存在50毫秒 b.start(); /* Output(循環(huán)): A begin 1484319778766 B beg

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論