久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

java線程安全總結(jié)

 干掉熊貓,,我就是國寶 2010-11-12
最近想將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)一的,。其實java的多線程并發(fā)問題最終都會反映在java的內(nèi)存模型上,所謂線程安全無非是要控制多個線程對某個資源的有序訪問或修改,??偨Y(jié)java的內(nèi)存模型,要解決兩個主要的問題:可見性和有序性,。我們都知道計算機(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實現(xiàn)系統(tǒng)決定,。
        線程不能直接為主存中中字段賦值,,它會將值指定給工作內(nèi)存中的變量副本(assign),完成后這個變量副本會同步到主存儲區(qū)(store-write),至于何時同步過去,,根據(jù)JVM實現(xiàn)系統(tǒng)決定.有該字段,則會從主內(nèi)存中將該字段賦值到工作內(nèi)存中,這個過程為read-load,完成后線程會引用該變量副本,,當(dāng)同一線程多次重復(fù)對字段賦值時,比如:

Java代碼 復(fù)制代碼
  1. for(int i=0;i<10;i++)   
  2.  a++;  
 


線程有可能只對工作內(nèi)存中的副本進(jìn)行賦值,只到最后一次賦值后才同步到主存儲區(qū),所以assign,store,weite順序可以由JVM實現(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是一個原子操作,。看看下面代碼:

Java代碼 復(fù)制代碼
  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é)果都是不確定的,,因為線程的執(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代碼 復(fù)制代碼
  1. synchronized(鎖){   
  2.      臨界區(qū)代碼   
  3. }   
 


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

Java代碼 復(fù)制代碼
  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代碼 復(fù)制代碼
  1. synchronized(鎖){   
  2. 臨界區(qū)代碼   
  3. }  
 


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

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


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


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

Java代碼 復(fù)制代碼
  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的阻塞隊列,。如果調(diào)用lock.notify()則會通知阻塞隊列的某個線程進(jìn)入就緒隊列。
聲明一個盤子,,只能放一個雞蛋

 
Java代碼 復(fù)制代碼
  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.         if (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();// 喚醒阻塞隊列的某線程到就緒隊列   
  19.         System.out.println("拿到雞蛋");   
  20.         return egg;   
  21.     }   
  22.   
  23.     public synchronized void putEgg(Object egg) {   
  24.         if (eggs.size() > 0) {   
  25.             try {   
  26.                 wait();   
  27.             } catch (InterruptedException e) {   
  28.             }   
  29.         }   
  30.         eggs.add(egg);// 往盤子里放雞蛋   
  31.         notify();// 喚醒阻塞隊列的某線程到就緒隊列   
  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代碼 復(fù)制代碼
  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()方法,喚醒鎖的阻塞隊列的線程,,此時阻塞隊列還沒有線程,。
2 又有一個A線程對象調(diào)用plate.putEgg方法,此時eggs.size()不為0,,調(diào)用wait()方法,,自己進(jìn)入了鎖對象的阻塞隊列。
3 此時,,來了一個B線程對象,,調(diào)用plate.getEgg方法,eggs.size()不為0,,順利的拿到了一個雞蛋,,還執(zhí)行了notify()方法,喚醒鎖的阻塞隊列的線程,,此時阻塞隊列有一個A線程對象,,喚醒后,它進(jìn)入到就緒隊列,,就緒隊列也就它一個,,因此馬上得到鎖,開始往盤子里放雞蛋,,此時盤子是空的,,因此放雞蛋成功。
4 假設(shè)接著來了線程A,,就重復(fù)2,;假設(shè)來料線程B,就重復(fù)3,。

整個過程都保證了放雞蛋,,拿雞蛋,放雞蛋,,拿雞蛋,。



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

Java代碼 復(fù)制代碼
  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的值不一定是正確的,,因為a=a+count包含了好幾步操作,而此時多個線程的執(zhí)行是無序的,,因為沒有任何機(jī)制來保證多個線程的執(zhí)行有序性和原子性,。volatile存在的意義是,任何線程對a的修改,,都會馬上被其他線程讀取到,,因為直接操作主存,沒有線程對工作內(nèi)存和主存的同步,。所以,,volatile的使用場景是有限的,在有限的一些情形下可以使用 volatile 變量替代鎖,。要使 volatile 變量提供理想的線程安全,必須同時滿足下面兩個條件:
1)對變量的寫操作不依賴于當(dāng)前值,。
2)該變量沒有包含在具有其他變量的不變式中

volatile只保證了可見性,所以Volatile適合直接賦值的場景,,如

Java代碼 復(fù)制代碼
  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的最終值不一定是正確的,,因為this.a=a;涉及到給a賦值和將a同步回主存的步驟,,這個順序可能被打亂。如果用volatile聲明了,,讀取主存副本到工作內(nèi)存和同步a到主存的步驟,,相當(dāng)于是一個原子操作。所以簡單來說,,volatile適合這種場景:一個變量被多個線程共享,,線程直接給這個變量賦值。這是一種很簡單的同步場景,,這時候使用volatile的開銷將會非常小,。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多