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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

java线程安全总结 - 1 (转载)

發(fā)布時間:2023/11/27 生活经验 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java线程安全总结 - 1 (转载) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

原文地址:http://www.jameswxx.com/java/java%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E6%80%BB%E7%BB%93/

最近想將java基礎(chǔ)的一些東西都整理整理,寫下來,這是對知識的總結(jié),也是一種樂趣。已經(jīng)擬好了提綱,大概分為這幾個主題:?java線程安全,java垃圾收集,java并發(fā)包詳細(xì)介紹,java profile和jvm性能調(diào)優(yōu)?。慢慢寫吧。本人jameswxx原創(chuàng)文章,轉(zhuǎn)載請注明出處,我費(fèi)了很多心血,多謝了。關(guān)于java線 程安全,網(wǎng)上有很多資料,我只想從自己的角度總結(jié)對這方面的考慮,有時候?qū)憱|西是很痛苦的,知道一些東西,但想用文字說清楚,卻不是那么容易。我認(rèn)為要認(rèn) 識java線程安全,必須了解兩個主要的點(diǎn):java的內(nèi)存模型,java的線程同步機(jī)制。特別是內(nèi)存模型,java的線程同步機(jī)制很大程度上都是基于內(nèi) 存模型而設(shè)定的。后面我還會寫java并發(fā)包的文章,詳細(xì)總結(jié)如何利用java并發(fā)包編寫高效安全的多線程并發(fā)程序。暫時寫得比較倉促,后面會慢慢補(bǔ)充完 善。

?

淺談java內(nèi)存模型?
?????? 不同的平臺,內(nèi)存模型是不一樣的,但是jvm的內(nèi)存模型規(guī)范是統(tǒng)一的。其實(shí)java的多線程并發(fā)問題最終都會反映在java的內(nèi)存模型上,所謂線程安全無 非是要控制多個線程對某個資源的有序訪問或修改。總結(jié)java的內(nèi)存模型,要解決兩個主要的問題:可見性和有序性。我們都知道計(jì)算機(jī)有高速緩存的存在,處 理器并不是每次處理數(shù)據(jù)都是取內(nèi)存的。JVM定義了自己的內(nèi)存模型,屏蔽了底層平臺內(nèi)存管理細(xì)節(jié),對于java開發(fā)人員,要清楚在jvm內(nèi)存模型的基礎(chǔ) 上,如果解決多線程的可見性和有序性。
???????那么,何謂可見性??多個線程之間是不能互相傳遞數(shù)據(jù)通信的,它們之間的溝通只能通過共享變量來進(jìn)行。Java內(nèi)存模型(JMM)規(guī)定了jvm有主內(nèi)存,主內(nèi)存是多個線程共享 的。當(dāng)new一個對象的時候,也是被分配在主內(nèi)存中,每個線程都有自己的工作內(nèi)存,工作內(nèi)存存儲了主存的某些對象的副本,當(dāng)然線程的工作內(nèi)存大小是有限制 的。當(dāng)線程操作某個對象時,執(zhí)行順序如下:
?(1) 從主存復(fù)制變量到當(dāng)前工作內(nèi)存 (read and load)
?(2) 執(zhí)行代碼,改變共享變量值 (use and assign)
?(3) 用工作內(nèi)存數(shù)據(jù)刷新主存相關(guān)內(nèi)容 (store and write)

JVM規(guī)范定義了線程對主存的操作指 令:read,load,use,assign,store,write。當(dāng)一個共享變量在多個線程的工作內(nèi)存中都有副本時,如果一個線程修改了這個共享 變量,那么其他線程應(yīng)該能夠看到這個被修改后的值,這就是多線程的可見性問題。
????????那么,什么是有序性呢??線程在引用變量時不能直接從主內(nèi)存中引用,如果線程工作內(nèi)存中沒有該變量,則會從主內(nèi)存中拷貝一個副本到工作內(nèi)存中,這個過程為read-load,完 成后線程會引用該副本。當(dāng)同一線程再度引用該字段時,有可能重新從主存中獲取變量副本(read-load-use),也有可能直接引用原來的副本 (use),也就是說 read,load,use順序可以由JVM實(shí)現(xiàn)系統(tǒng)決定。
? ????? 線程不能直接為主存中中字段賦值,它會將值指定給工作內(nèi)存中的變量副本(assign),完成后這個變量副本會同步到主存儲區(qū)(store- write),至于何時同步過去,根據(jù)JVM實(shí)現(xiàn)系統(tǒng)決定.有該字段,則會從主內(nèi)存中將該字段賦值到工作內(nèi)存中,這個過程為read-load,完成后線 程會引用該變量副本,當(dāng)同一線程多次重復(fù)對字段賦值時,比如:

Java代碼??
  1. for(int?i=0;i<10;i++)??
  2. ?a++;??

?


線程有可能只對工作內(nèi)存中的副本進(jìn)行賦值,只到最后一次賦值后才同步到主存儲區(qū),所以assign,store,weite順序可以由JVM實(shí)現(xiàn)系統(tǒng)決 定。假設(shè)有一個共享變量x,線程a執(zhí)行x=x+1。從上面的描述中可以知道x=x+1并不是一個原子操作,它的執(zhí)行過程如下:
1 從主存中讀取變量x副本到工作內(nèi)存
2 給x加1
3 將x加1后的值寫回主?存
如果另外一個線程b執(zhí)行x=x-1,執(zhí)行過程如下:
1 從主存中讀取變量x副本到工作內(nèi)存
2 給x減1
3 將x減1后的值寫回主存?
那么顯然,最終的x的值是不可靠的。假設(shè)x現(xiàn)在為10,線程a加1,線程b減1,從表面上看,似乎最終x還是為10,但是多線程情況下會有這種情況發(fā)生:
1:線程a從主存讀取x副本到工作內(nèi)存,工作內(nèi)存中x值為10
2:線程b從主存讀取x副本到工作內(nèi)存,工作內(nèi)存中x值為10
3:線程a將工作內(nèi)存中x加1,工作內(nèi)存中x值為11
4:線程a將x提交主存中,主存中x為11
5:線程b將工作內(nèi)存中x值減1,工作內(nèi)存中x值為9
6:線程b將x提交到中主存中,主存中x為9?
同樣,x有可能為11,如果x是一個銀行賬戶,線程a存款,線程b扣款,顯然這樣是有嚴(yán)重問題的,要解決這個問題,必須保證線程a和線程b是有序執(zhí)行的, 并且每個線程執(zhí)行的加1或減1是一個原子操作??纯聪旅娲a:

Java代碼??
  1. public?class?Account?{??
  2. ??
  3. ????private?int?balance;??
  4. ??
  5. ????public?Account(int?balance)?{??
  6. ????????this.balance?=?balance;??
  7. ????}??
  8. ??
  9. ????public?int?getBalance()?{??
  10. ????????return?balance;??
  11. ????}??
  12. ??
  13. ????public?void?add(int?num)?{??
  14. ????????balance?=?balance?+?num;??
  15. ????}??
  16. ??
  17. ????public?void?withdraw(int?num)?{??
  18. ????????balance?=?balance?-?num;??
  19. ????}??
  20. ??
  21. ????public?static?void?main(String[]?args)?throws?InterruptedException?{??
  22. ????????Account?account?=?new?Account(1000);??
  23. ????????Thread?a?=?new?Thread(new?AddThread(account,?20),?"add");??
  24. ????????Thread?b?=?new?Thread(new?WithdrawThread(account,?20),?"withdraw");??
  25. ????????a.start();??
  26. ????????b.start();??
  27. ????????a.join();??
  28. ????????b.join();??
  29. ????????System.out.println(account.getBalance());??
  30. ????}??
  31. ??
  32. ????static?class?AddThread?implements?Runnable?{??
  33. ????????Account?account;??
  34. ????????int?????amount;??
  35. ??
  36. ????????public?AddThread(Account?account,?int?amount)?{??
  37. ????????????this.account?=?account;??
  38. ????????????this.amount?=?amount;??
  39. ????????}??
  40. ??
  41. ????????public?void?run()?{??
  42. ????????????for?(int?i?=?0;?i?<?200000;?i++)?{??
  43. ????????????????account.add(amount);??
  44. ????????????}??
  45. ????????}??
  46. ????}??
  47. ??
  48. ????static?class?WithdrawThread?implements?Runnable?{??
  49. ????????Account?account;??
  50. ????????int?????amount;??
  51. ??
  52. ????????public?WithdrawThread(Account?account,?int?amount)?{??
  53. ????????????this.account?=?account;??
  54. ????????????this.amount?=?amount;??
  55. ????????}??
  56. ??
  57. ????????public?void?run()?{??
  58. ????????????for?(int?i?=?0;?i?<?100000;?i++)?{??
  59. ????????????????account.withdraw(amount);??
  60. ????????????}??
  61. ????????}??
  62. ????}??
  63. }??

?


第一次執(zhí)行結(jié)果為10200,第二次執(zhí)行結(jié)果為1060,每次執(zhí)行的結(jié)果都是不確定的,因?yàn)榫€程的執(zhí)行順序是不可預(yù)見的。這是java同步產(chǎn)生的根 源,synchronized關(guān)鍵字保證了多個線程對于同步塊是互斥的,synchronized作為一種同步手段,解決java多線程的執(zhí)行有序性和內(nèi) 存可見性,而volatile關(guān)鍵字之解決多線程的內(nèi)存可見性問題。后面將會詳細(xì)介紹。

?


synchronized關(guān)鍵 字?
??????? 上面說了,java用synchronized關(guān)鍵字做為多線程并發(fā)環(huán)境的執(zhí)行有序性的保證手段之一。當(dāng)一段代碼會修改共享變量,這一段代碼成為互斥區(qū)或 臨界區(qū),為了保證共享變量的正確性,synchronized標(biāo)示了臨界區(qū)。典型的用法如下:

Java代碼??
  1. synchronized(鎖){??
  2. ?????臨界區(qū)代碼??
  3. }???

?


為了保證銀行賬戶的安全,可以操作賬戶的方法如下:

Java代碼??
  1. public?synchronized?void?add(int?num)?{??
  2. ?????balance?=?balance?+?num;??
  3. }??
  4. public?synchronized?void?withdraw(int?num)?{??
  5. ?????balance?=?balance?-?num;??
  6. }??

?


剛才不是說了synchronized的用法是這樣的嗎:

Java代碼??
  1. synchronized(鎖){??
  2. 臨界區(qū)代碼??
  3. }??

?


那么對于public synchronized void add(int num)這種情況,意味著什么呢?其實(shí)這種情況,鎖就是這個方法所在的對象。同理,如果方法是public? static synchronized void add(int num),那么鎖就是這個方法所在的class。
??????? 理論上,每個對象都可以做為鎖,但一個對象做為鎖時,應(yīng)該被多個線程共享,這樣才顯得有意義,在并發(fā)環(huán)境下,一個沒有共享的對象作為鎖是沒有意義的。假如 有這樣的代碼:

Java代碼??
  1. public?class?ThreadTest{??
  2. ??public?void?test(){??
  3. ?????Object?lock=new?Object();??
  4. ?????synchronized?(lock){??
  5. ????????//do?something??
  6. ?????}??
  7. ??}??
  8. }??

?


lock變量作為一個鎖存在根本沒有意義,因?yàn)樗静皇枪蚕韺ο?#xff0c;每個線程進(jìn)來都會執(zhí)行Object lock=new Object();每個線程都有自己的lock,根本不存在鎖競爭。
??????? 每個鎖對象都有兩個隊(duì)列,一個是就緒隊(duì)列,一個是阻塞隊(duì)列,就緒隊(duì)列存儲了將要獲得鎖的線程,阻塞隊(duì)列存儲了被阻塞的線程,當(dāng)一個被線程被喚醒 (notify)后,才會進(jìn)入到就緒隊(duì)列,等待cpu的調(diào)度。當(dāng)一開始線程a第一次執(zhí)行account.add方法時,jvm會檢查鎖對象account 的就緒隊(duì)列是否已經(jīng)有線程在等待,如果有則表明account的鎖已經(jīng)被占用了,由于是第一次運(yùn)行,account的就緒隊(duì)列為空,所以線程a獲得了鎖, 執(zhí)行account.add方法。如果恰好在這個時候,線程b要執(zhí)行account.withdraw方法,因?yàn)榫€程a已經(jīng)獲得了鎖還沒有釋放,所以線程 b要進(jìn)入account的就緒隊(duì)列,等到得到鎖后才可以執(zhí)行。
一個線程執(zhí)行臨界區(qū)代碼過程如下:
1 獲得同步鎖
2 清空工作內(nèi)存
3 從主存拷貝變量副本到工作內(nèi)存
4 對這些變量計(jì)算
5 將變量從工作內(nèi)存寫回到主存
6 釋放鎖
可見,synchronized既保證了多線程的并發(fā)有序性,又保證了多線程的內(nèi)存可見性。


生產(chǎn)者/消費(fèi)者模式?
??????? 生產(chǎn)者/消費(fèi)者模式其實(shí)是一種很經(jīng)典的線程同步模型,很多時候,并不是光保證多個線程對某共享資源操作的互斥性就夠了,往往多個線程之間都是有協(xié)作的。
??????? 假設(shè)有這樣一種情況,有一個桌子,桌子上面有一個盤子,盤子里只能放一顆雞蛋,A專門往盤子里放雞蛋,如果盤子里有雞蛋,則一直等到盤子里沒雞蛋,B專門 從盤子里拿雞蛋,如果盤子里沒雞蛋,則等待直到盤子里有雞蛋。其實(shí)盤子就是一個互斥區(qū),每次往盤子放雞蛋應(yīng)該都是互斥的,A的等待其實(shí)就是主動放棄鎖,B 等待時還要提醒A放雞蛋。
如何讓線程主動釋放鎖
很簡單,調(diào)用鎖的wait()方法就好。wait方法是從Object來的,所以任意對象都有這個方法。看這個代碼片段:

Java代碼??
  1. Object?lock=new?Object();//聲明了一個對象作為鎖??
  2. ???synchronized?(lock)?{??
  3. ???????balance?=?balance?-?num;??
  4. ???????//這里放棄了同步鎖,好不容易得到,又放棄了??
  5. ???????lock.wait();??
  6. }??

?


如果一個線程獲得了鎖lock,進(jìn)入了同步塊,執(zhí)行l(wèi)ock.wait(),那么這個線程會進(jìn)入到lock的阻塞隊(duì)列。如果調(diào)用 lock.notify()則會通知阻塞隊(duì)列的某個線程進(jìn)入就緒隊(duì)列。
聲明一個盤子,只能放一個雞蛋

?

Java代碼??
  1. import?java.util.ArrayList;??
  2. import?java.util.List;??
  3. ??
  4. public?class?Plate?{??
  5. ??
  6. ????List<Object>?eggs?=?new?ArrayList<Object>();??
  7. ??
  8. ????public?synchronized?Object?getEgg()?{??
  9. ????????while(eggs.size()?==?0)?{??
  10. ????????????try?{??
  11. ????????????????wait();??
  12. ????????????}?catch?(InterruptedException?e)?{??
  13. ????????????}??
  14. ????????}??
  15. ??
  16. ????????Object?egg?=?eggs.get(0);??
  17. ????????eggs.clear();//?清空盤子??
  18. ????????notify();//?喚醒阻塞隊(duì)列的某線程到就緒隊(duì)列??
  19. ????????System.out.println("拿到雞蛋");??
  20. ????????return?egg;??
  21. ????}??
  22. ??
  23. ????public?synchronized?void?putEgg(Object?egg)?{??
  24. ????????while(eggs.size()?>?0)?{??
  25. ????????????try?{??
  26. ????????????????wait();??
  27. ????????????}?catch?(InterruptedException?e)?{??
  28. ????????????}??
  29. ????????}??
  30. ????????eggs.add(egg);//?往盤子里放雞蛋??
  31. ????????notify();//?喚醒阻塞隊(duì)列的某線程到就緒隊(duì)列??
  32. ????????System.out.println("放入雞蛋");??
  33. ????}??
  34. ??????
  35. ????static?class?AddThread?extends?Thread{??
  36. ????????private?Plate?plate;??
  37. ????????private?Object?egg=new?Object();??
  38. ????????public?AddThread(Plate?plate){??
  39. ????????????this.plate=plate;??
  40. ????????}??
  41. ??????????
  42. ????????public?void?run(){??
  43. ????????????for(int?i=0;i<5;i++){??
  44. ????????????????plate.putEgg(egg);??
  45. ????????????}??
  46. ????????}??
  47. ????}??
  48. ??????
  49. ????static?class?GetThread?extends?Thread{??
  50. ????????private?Plate?plate;??
  51. ????????public?GetThread(Plate?plate){??
  52. ????????????this.plate=plate;??
  53. ????????}??
  54. ??????????
  55. ????????public?void?run(){??
  56. ????????????for(int?i=0;i<5;i++){??
  57. ????????????????plate.getEgg();??
  58. ????????????}??
  59. ????????}??
  60. ????}??
  61. ??????
  62. ????public?static?void?main(String?args[]){??
  63. ????????try?{??
  64. ????????????Plate?plate=new?Plate();??
  65. ????????????Thread?add=new?Thread(new?AddThread(plate));??
  66. ????????????Thread?get=new?Thread(new?GetThread(plate));??
  67. ????????????add.start();??
  68. ????????????get.start();??
  69. ????????????add.join();??
  70. ????????????get.join();??
  71. ????????}?catch?(InterruptedException?e)?{??
  72. ????????????e.printStackTrace();??
  73. ????????}??
  74. ????????System.out.println("測試結(jié)束");??
  75. ????}??
  76. }??

? 執(zhí)行結(jié)果:

Html代碼??
  1. 放入雞蛋??
  2. 拿到雞蛋??
  3. 放入雞蛋??
  4. 拿到雞蛋??
  5. 放入雞蛋??
  6. 拿到雞蛋??
  7. 放入雞蛋??
  8. 拿到雞蛋??
  9. 放入雞蛋??
  10. 拿到雞蛋??
  11. 測試結(jié)束??

?

?


聲明一個Plate對象為plate,被線程A和線程B共享,A專門放雞蛋,B專門拿雞蛋。假設(shè)
1 開始,A調(diào)用plate.putEgg方法,此時eggs.size()為0,因此順利將雞蛋放到盤子,還執(zhí)行了notify()方法,喚醒鎖的阻塞隊(duì)列 的線程,此時阻塞隊(duì)列還沒有線程。
2 又有一個A線程對象調(diào)用plate.putEgg方法,此時eggs.size()不為0,調(diào)用wait()方法,自己進(jìn)入了鎖對象的阻塞隊(duì)列。
3 此時,來了一個B線程對象,調(diào)用plate.getEgg方法,eggs.size()不為0,順利的拿到了一個雞蛋,還執(zhí)行了notify()方法,喚 醒鎖的阻塞隊(duì)列的線程,此時阻塞隊(duì)列有一個A線程對象,喚醒后,它進(jìn)入到就緒隊(duì)列,就緒隊(duì)列也就它一個,因此馬上得到鎖,開始往盤子里放雞蛋,此時盤子是 空的,因此放雞蛋成功。
4 假設(shè)接著來了線程A,就重復(fù)2;假設(shè)來料線程B,就重復(fù)3。?
整個過程都保證了放雞蛋,拿雞蛋,放雞蛋,拿雞蛋。

?


volatile關(guān)鍵字?
?????? volatile是java提供的一種同步手段,只不過它是輕量級的同步,為什么這么說,因?yàn)関olatile只能保證多線程的內(nèi)存可見性,不能保證多線 程的執(zhí)行有序性。而最徹底的同步要保證有序性和可見性,例如synchronized。任何被volatile修飾的變量,都不拷貝副本到工作內(nèi)存,任何 修改都及時寫在主存。因此對于Valatile修飾的變量的修改,所有線程馬上就能看到,但是volatile不能保證對變量的修改是有序的。什么意思 呢?假如有這樣的代碼:

Java代碼??
  1. public?class?VolatileTest{??
  2. ??public?volatile?int?a;??
  3. ??public?void?add(int?count){??
  4. ???????a=a+count;??
  5. ??}??
  6. }??

?


??????? 當(dāng)一個VolatileTest對象被多個線程共享,a的值不一定是正確的,因?yàn)閍=a+count包含了好幾步操作,而此時多個線程的執(zhí)行是無序的,因 為沒有任何機(jī)制來保證多個線程的執(zhí)行有序性和原子性。volatile存在的意義是,任何線程對a的修改,都會馬上被其他線程讀取到,因?yàn)橹苯硬僮髦鞔?#xff0c; 沒有線程對工作內(nèi)存和主存的同步。所以,volatile的使用場景是有限的,在有限的一些情形下可以使用 volatile 變量替代鎖。要使 volatile 變量提供理想的線程安全,必須同時滿足下面兩個條件:
1)對 變量的寫操作不依賴于當(dāng)前值。
2)該變量沒有包含在具有其他變量的不變式中?
volatile只保證了可見性,所以Volatile適合直接賦值的場景,如

Java代碼??
  1. public?class?VolatileTest{??
  2. ??public?volatile?int?a;??
  3. ??public?void?setA(int?a){??
  4. ??????this.a=a;??
  5. ??}??
  6. }??

?


在沒有volatile聲明時,多線程環(huán)境下,a的最終值不一定是正確的,因?yàn)閠his.a=a;涉及到給a賦值和將a同步回主存的步驟,這個順序可能被 打亂。如果用volatile聲明了,讀取主存副本到工作內(nèi)存和同步a到主存的步驟,相當(dāng)于是一個原子操作。所以簡單來說,volatile適合這種場 景:一個變量被多個線程共享,線程直接給這個變量賦值。這是一種很簡單的同步場景,這時候使用volatile的開銷將會非常小。

轉(zhuǎn)載于:https://www.cnblogs.com/peak-c/p/6557935.html

總結(jié)

以上是生活随笔為你收集整理的java线程安全总结 - 1 (转载)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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