日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

java 多线程同步问题_Java多线程同步问题:一个小Demo完全搞懂

發(fā)布時間:2023/12/15 java 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 多线程同步问题_Java多线程同步问题:一个小Demo完全搞懂 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

版權(quán)聲明:本文出自汪磊的博客,轉(zhuǎn)載請務(wù)必注明出處。

Java線程系列文章只是自己知識的總結(jié)梳理,都是最基礎(chǔ)的玩意,已經(jīng)掌握熟練的可以繞過。

一、一個簡單的Demo引發(fā)的血案

關(guān)于線程同步問題我們從一個簡單的Demo現(xiàn)象說起。Demo特別簡單就是開啟兩個線程打印字符串信息。

OutPutStr類源碼:

1 public classOutPutStr {2

3 public voidout(String str) {4 for (int i = 0; i < str.length(); i++) {5 System.out.print(str.charAt(i));6 }7 System.out.println();8 }9 }

很簡單吧,就是一個方法供外界調(diào)用,調(diào)用的時候傳進(jìn)來一個字符串,方法逐個取出字符串的字符并打印到控制臺。

接下來,我們看main方法中邏輯:

1 public static voidmain(String[] args) {2 //3 final OutPutStr o = newOutPutStr();4 new Thread(newRunnable() {5

6 @Override7 public voidrun() {8 //9 while(true){10 o.out("111111111111");11 }12 }13 }).start();14 new Thread(newRunnable() {15

16 @Override17 public voidrun() {18 //19 while(true){20 o.out("222222222222");21 }22 }23 }).start();24 }

也很簡單,就是開啟兩個線程分別調(diào)用OutPutStr中out方法不停打印字符串信息,運(yùn)行程序打印信息如下:

1 222222222222

2 222222222222

3 22222222222111111111

4 2

5 111111111111

6 111111111111

7 1111222222222211111111

8 111111111111

咦?和我們想的不一樣啊,怎么還會打印出22222222222111111111這樣子的信息,這是怎么回事呢?

二、原因解析

我們知道線程的執(zhí)行是CPU隨機(jī)調(diào)度的,比如我們開啟10個線程,這10個線程并不是同時執(zhí)行的,而是CPU快速的在這10個線程之間切換執(zhí)行,由于切換速度極快使我們感覺同時執(zhí)行罷了。發(fā)生上面問題的本質(zhì)就是CPU對線程執(zhí)行的隨機(jī)調(diào)度,比如A線程此時正在打印信息還沒打印完畢此時CPU切換到B線程執(zhí)行了,B線程執(zhí)行完了又切換回A線程執(zhí)行就會導(dǎo)致上面現(xiàn)象發(fā)生。

線程同步問題往往發(fā)生在多個線程調(diào)用同一方法或者操作同一變量,但是我們要知道其本質(zhì)就是CPU對線程的隨機(jī)調(diào)度,CPU無法保證一個線程執(zhí)行完其邏輯才去調(diào)用另一個線程執(zhí)行。

三、同步方法解決上述問題

既然知道了問題發(fā)生的原因,記下來我們就要想辦法解決問題啊,解決的思路就是保證一個線程在調(diào)用out方法的時候如果沒執(zhí)行完那么另一個不能執(zhí)行此方法,換句話說就是只能等待別的線程執(zhí)行完畢才能執(zhí)行。

針對線程同步問題java早就有解決方法了,最簡單的就是給方法加上synchronized關(guān)鍵字,如下:

1 public synchronized voidout(String str) {2 for (int i = 0; i < str.length(); i++) {3 System.out.print(str.charAt(i));4 }5 System.out.println();6 }

這是什么意思呢?加上synchronized關(guān)鍵字后,比如A線程執(zhí)行out方法就相當(dāng)于拿到了一把鎖,只有獲取這個鎖才能執(zhí)行此方法,如果在A線程執(zhí)行out方法過程中B線程也想插一腳進(jìn)來執(zhí)行out方法,對不起此時這是不能夠的,因?yàn)榇藭r鎖在A線程手里,B線程無權(quán)拿到這把鎖,只有等到A線程執(zhí)行完后放棄鎖,B線程才能拿到鎖執(zhí)行out方法。

為out方法加上synchronized后其就變成了同步方法,普通同步方法的鎖是this,也就是當(dāng)前對象,比如demo中,外部要想調(diào)用out方法就必須創(chuàng)建OutPutStr類實(shí)例對象o,此時out同步方法的鎖就是這個o。

四、同步代碼塊解決上述問題

我們也可以利用同步代碼塊解決上述問題,修改out方法如下:

1 public voidout(String str) {2 synchronized (this) {3 for (int i = 0; i < str.length(); i++) {4 System.out.print(str.charAt(i));5 }6 System.out.println();7 }8 }

同步代碼塊寫法:synchronized(obj){},其中obj為鎖對象,此處我們傳入this,同樣方法的鎖也為當(dāng)前對象,如果此處我們傳入str,那么這里的鎖就是str對象了。

為了說明不同鎖帶來的影響我們修改OutPutStr代碼如下:

1 public classOutPutStr {2

3 public synchronized voidout(String str) {4 for (int i = 0; i < str.length(); i++) {5 System.out.print(str.charAt(i));6 }7 System.out.println();8 }9

10 public voidout1(String str) {11

12 synchronized(str) {13 for (int i = 0; i < str.length(); i++) {14 System.out.print(str.charAt(i));15 }16 System.out.println();17 }18 }19 }

很簡單我們就是加入了一個out1方法,out方法用同步函數(shù)保證同步,out1用同步代碼塊保證代碼塊,但是鎖我們用的是str。

main代碼:

1 public static voidmain(String[] args) {2 //3 final OutPutStr o = newOutPutStr();4 new Thread(newRunnable() {5

6 @Override7 public voidrun() {8 //9 while(true){10 o.out("111111111111");11 }12 }13 }).start();14 new Thread(newRunnable() {15

16 @Override17 public voidrun() {18 //19 while(true){20 o.out1("222222222222");21 }22 }23 }).start();24 }

也沒什么,就是其中一個線程調(diào)用out方法,另一個調(diào)用out1方法,運(yùn)行程序:

111111111111222

222222222222

111111111111222222222222

222222222222

看到了吧,打印信息又出問題了,就是因?yàn)閛ut與out1方法的鎖不一樣導(dǎo)致的,線程A調(diào)用out方法拿到this這把鎖,線程B調(diào)用out1拿到str這把鎖,二者互不影響,解決辦法也很簡單,修改out1方法如下即可:

1 public voidout1(String str) {2

3 synchronized (this) {4 for (int i = 0; i < str.length(); i++) {5 System.out.print(str.charAt(i));6 }7 System.out.println();8 }9 }

五、靜態(tài)函數(shù)的同步問題

我們繼續(xù)修改OutPutStr類,加入out2方法:

1 public classOutPutStr {2

3 public synchronized voidout(String str) {4 for (int i = 0; i < str.length(); i++) {5 System.out.print(str.charAt(i));6 }7 System.out.println();8 }9

10 public voidout1(String str) {11

12 synchronized (this) {13 for (int i = 0; i < str.length(); i++) {14 System.out.print(str.charAt(i));15 }16 System.out.println();17 }18 }19

20 public synchronized static voidout2(String str) {21

22 for (int i = 0; i < str.length(); i++) {23 System.out.print(str.charAt(i));24 }25 System.out.println();26 }27 }

main中兩個子線程分別調(diào)用out1,ou2打印信息,運(yùn)行程序打印信息如下;

1 222222222222

2 222222222222

3 222222222111111111111

4 111111111111

咦?又出錯了,out2與out方法唯一不同就是out2就是靜態(tài)方法啊,不是說同步方法鎖是this嗎,是啊,沒錯,但是靜態(tài)方法沒有對應(yīng)類的實(shí)例對象依然可以調(diào)用,那其鎖是誰呢?顯然靜態(tài)方法鎖不是this,這里就直說了,是類的字節(jié)碼對象,類的字節(jié)碼對象是優(yōu)先于類實(shí)例對象存在的。

將ou1方法改為如下:

1 public voidout1(String str) {2

3 synchronized (OutPutStr.class) {4 for (int i = 0; i < str.length(); i++) {5 System.out.print(str.charAt(i));6 }7 System.out.println();8 }9 }

再次運(yùn)行程序,就會發(fā)現(xiàn)信息能正常打印了。

六、synchronized同步方式總結(jié)

到此我們就該小小的總結(jié)一下了,普通同步函數(shù)的鎖是this,當(dāng)前類實(shí)例對象,同步代碼塊鎖可以自己定義,靜態(tài)同步函數(shù)的鎖是類的字節(jié)碼文件。總結(jié)完畢,就是這么簡單。說了一大堆理解這一句就夠了。

七、JDK1.5中Lock鎖機(jī)制解決線程同步

大家是不是覺得上面說的鎖這個玩意咋這么抽象,看不見,摸不著的。從JDK1.5起我們就可以根據(jù)需要顯性的獲取鎖以及釋放鎖了,這樣也更加符合面向?qū)ο笤瓌t。

Lock接口的實(shí)現(xiàn)子類之一ReentrantLock,翻譯過來就是重入鎖,就是支持重新進(jìn)入的鎖,該鎖能夠支持一個線程對資源的重復(fù)加鎖,也就是說在調(diào)用lock()方法時,已經(jīng)獲取到鎖的線程,能夠再次調(diào)用lock()方法獲取鎖而不被阻塞,同時還支持獲取鎖的公平性和非公平性,所謂公平性就是多個線程發(fā)起lock()請求,先發(fā)起的線程優(yōu)先獲取執(zhí)行權(quán),非公平性就是獲取鎖與是否優(yōu)先發(fā)起lock()操作無關(guān)。默認(rèn)情況下是不公平的鎖,為什么要這樣設(shè)計(jì)呢?現(xiàn)實(shí)生活中我們都希望公平的啊?我們想一下,現(xiàn)實(shí)生活中要保證公平就必須額外開銷,比如地鐵站保證有序公平進(jìn)站就必須配備額外人員維持秩序,程序中也是一樣保證公平就必須需要額外開銷,這樣性能就下降了,所以公平與性能是有一定矛盾的,除非公平策略對你的程序很重要,比如必須按照順序執(zhí)行線程,否則還是使用不公平鎖為好。

接下來我們修改OutPutStr類,添加out3方法:

1 //true表示公平鎖,false非公平鎖

2 private Lock lock = new ReentrantLock();3

4 public voidout3(String str) {5

6 lock.lock();//如果有其它線程已經(jīng)獲取鎖,那么當(dāng)前線程在此等待直到其它線程釋放鎖。

7 try{8 for (int i = 0; i < str.length(); i++) {9 System.out.print(str.charAt(i));10 }11 System.out.println();12 } finally{13 lock.unlock();//釋放鎖資源,之所以加入try{}finally{}代碼塊,14 //是為了保證鎖資源的釋放,如果代碼發(fā)生異常也可以保證鎖資源的釋放,15 //否則其它線程無法拿到鎖資源執(zhí)行業(yè)務(wù)邏輯,永遠(yuǎn)處于等待狀態(tài)。

16 }17 }

關(guān)鍵注釋都在代碼中有所體現(xiàn)了,使用起來也很簡單。

八、Lock與synchronized同步方式優(yōu)缺點(diǎn)

Lock 的鎖定是通過代碼實(shí)現(xiàn)的,而 synchronized 是在 JVM 層面上實(shí)現(xiàn)的(所有對象都自動含有單一的鎖。JVM負(fù)責(zé)跟蹤對象被加鎖的次數(shù)。如果一個對象被解鎖,其計(jì)數(shù)變?yōu)?。在線程第一次給對象加鎖的時候,計(jì)數(shù)變?yōu)?。每當(dāng)這個相同的線程在此對象上獲得鎖時,計(jì)數(shù)會遞增。只有首先獲得鎖的線程才能繼續(xù)獲取該對象上的多個鎖。每當(dāng)線程離開一個synchronized方法,計(jì)數(shù)遞減,當(dāng)計(jì)數(shù)為0的時候,鎖被完全釋放,此時別的線程就可以使用此資源)。

synchronized 在鎖定時如果方法塊拋出異常,JVM 會自動將鎖釋放掉,不會因?yàn)槌隽水惓]有釋放鎖造成線程死鎖。但是 Lock 的話就享受不到 JVM 帶來自動的功能,出現(xiàn)異常時必須在 finally 將鎖釋放掉,否則將會引起死鎖。

在資源競爭不是很激烈的情況下,偶爾會有同步的情形下,synchronized是很合適的。原因在于,編譯程序通常會盡可能的進(jìn)行優(yōu)化synchronize,另外可讀性非常好。在資源競爭激烈情況下,Lock同步機(jī)制性能會更好一些。

關(guān)于線程同步問題到這里就結(jié)束了,java多線程文章只是本人工作以來的一次梳理,都比較基礎(chǔ),但是卻很重要的,最近招人面試的最大體會就是都喜歡那些所謂時髦的技術(shù)一問基礎(chǔ)說的亂七八糟,浪費(fèi)彼此的時間。好啦,吐槽了幾句,本文到此為止,很基礎(chǔ)的玩意,希望對你有用。

聲明:文章將會陸續(xù)搬遷到個人公眾號,以后文章也會第一時間發(fā)布到個人公眾號,及時獲取文章內(nèi)容請關(guān)注公眾號

總結(jié)

以上是生活随笔為你收集整理的java 多线程同步问题_Java多线程同步问题:一个小Demo完全搞懂的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。