Java多線程匯總


1、多線程介紹

多線程優(yōu)點(diǎn)

  1. 資源利用率好
  2. 程序設(shè)計(jì)簡單
  3. 服務(wù)器響應(yīng)更快

多線程缺點(diǎn)

  1. 設(shè)計(jì)更復(fù)雜
  2. 上下文切換的開銷
  3. 增加資源消耗
    線程需要內(nèi)存維護(hù)本地的堆棧墓贿,同時(shí)需要操作系統(tǒng)資源管理線程茧泪。

2、并發(fā)模型

并發(fā)系統(tǒng)可以有多種并發(fā)模型聋袋,不同的并發(fā)模型在處理任務(wù)時(shí)队伟,線程間的協(xié)作和交互的方式也不同。

并行工作者

委托者將任務(wù)分配到不同的現(xiàn)場去執(zhí)行,每個(gè)工作者完成整個(gè)任務(wù)幽勒。工作者們并行運(yùn)作在不同的線程上嗜侮,甚至可能在不同的CPU上。如圖所示:


優(yōu)點(diǎn):很容易理解和使用啥容。
缺點(diǎn):

  • 共享狀態(tài)會(huì)很復(fù)雜


共享的工作者經(jīng)常需要訪問一些共享數(shù)據(jù)锈颗,無論是內(nèi)存中的或者共享的數(shù)據(jù)庫中的。

在并行工作者模型中咪惠,線程需要以某種方式存取共享數(shù)據(jù)击吱,以確保某個(gè)線程的修改能夠?qū)ζ渌€程可見。線程需要避免竟態(tài)遥昧,死鎖以及很多其他共享狀態(tài)的并發(fā)性問題覆醇。

  • 無狀態(tài)的工作者
    共享狀態(tài)能夠被系統(tǒng)中得其他線程修改。所以工作者在每次需要的時(shí)候必須重讀狀態(tài)渠鸽,以確保每次都能訪問到最新的副本叫乌,不管共享狀態(tài)是保存在內(nèi)存中的還是在外部數(shù)據(jù)庫中。工作者無法在內(nèi)部保存這個(gè)狀態(tài)(但是每次需要的時(shí)候可以重讀)稱為無狀態(tài)的徽缚。

每次都重讀需要的數(shù)據(jù)憨奸,將會(huì)導(dǎo)致速度變慢,特別是狀態(tài)保存在外部數(shù)據(jù)庫中的時(shí)候凿试。

  • 任務(wù)順序是不確定的
    作業(yè)執(zhí)行順序是不確定的排宰。無法保證哪個(gè)作業(yè)最先或者最后被執(zhí)行。

流水線模式

類似于工廠中生產(chǎn)線上的工人們那樣組織工作者那婉。每個(gè)工作者只負(fù)責(zé)作業(yè)中的部分工作板甘。當(dāng)完成了自己的這部分工作時(shí)工作者會(huì)將作業(yè)轉(zhuǎn)發(fā)給下一個(gè)工作者。每個(gè)工作者在自己的線程中運(yùn)行详炬,并且不會(huì)和其他工作者共享狀態(tài)盐类。有時(shí)也被成為無共享并行模型

通常使用非阻塞的IO來設(shè)計(jì)使用流水線并發(fā)模型的系統(tǒng)。非阻塞IO就是在跳,一旦某個(gè)工作者開始一個(gè)IO操作的時(shí)候(比如讀取文件或從網(wǎng)絡(luò)連接中讀取數(shù)據(jù))枪萄,這個(gè)工作者不會(huì)一直等待IO操作的結(jié)束。IO操作速度很慢猫妙,所以等待IO操作結(jié)束很浪費(fèi)CPU時(shí)間瓷翻。此時(shí)CPU可以做一些其他事情。當(dāng)IO操作完成的時(shí)候割坠,IO操作的結(jié)果(比如讀出的數(shù)據(jù)或者數(shù)據(jù)寫完的狀態(tài))被傳遞給下一個(gè)工作者齐帚。

在實(shí)際過程中,可能會(huì)是這樣:


也可能是這樣:


當(dāng)然還會(huì)有更復(fù)雜的設(shè)計(jì)彼哼,……

缺點(diǎn): 代碼編寫復(fù)雜,追蹤某個(gè)作業(yè)到底被什么代碼執(zhí)行難度較大对妄。
優(yōu)點(diǎn):

  • 無需共享的狀態(tài)

工作者之間無需共享狀態(tài),無需考慮所有因并發(fā)訪問共享對(duì)象而產(chǎn)生的并發(fā)性問題沪羔,基本上是一個(gè)單線程的實(shí)現(xiàn)饥伊。

  • 有狀態(tài)的工作者

當(dāng)工作者知道了沒有其他線程可以修改它們的數(shù)據(jù),工作者可以變成有狀態(tài)的蔫饰。對(duì)于有狀態(tài),是指愉豺,可以在內(nèi)存中保存它們需要操作的數(shù)據(jù)篓吁,只需在最后將更改寫回到外部存儲(chǔ)系統(tǒng)。因此蚪拦,有狀態(tài)的工作者通常比無狀態(tài)的工作者具有更高的性能杖剪。

  • 較好的硬件整合(Hardware Conformity)

當(dāng)能確定代碼只在單線程模式下執(zhí)行的時(shí)候,通常能夠創(chuàng)建更優(yōu)化的數(shù)據(jù)結(jié)構(gòu)和算法驰贷。單線程有狀態(tài)的工作者能夠在內(nèi)存中緩存數(shù)據(jù)盛嘿,訪問緩存的數(shù)據(jù)變得更快。

  • 合理的作業(yè)順序

基于流水線并發(fā)模型實(shí)現(xiàn)的并發(fā)系統(tǒng)括袒,在某種程度上是有可能保證作業(yè)的順序的次兆。作業(yè)的有序性使得它更容易地推出系統(tǒng)在某個(gè)特定時(shí)間點(diǎn)的狀態(tài)。更進(jìn)一步锹锰,你可以將所有到達(dá)的作業(yè)寫入到日志中去芥炭。一旦這個(gè)系統(tǒng)的某一部分掛掉了,該日志就可以用來重頭開始重建系統(tǒng)當(dāng)時(shí)的狀態(tài)恃慧。按照特定的順序?qū)⒆鳂I(yè)寫入日志园蝠,并按這個(gè)順序作為有保障的作業(yè)順序。

Actors

在Actor模型中每個(gè)工作者被稱為actor痢士。Actor之間可以直接異步地發(fā)送和處理消息彪薛。Actor可以被用來實(shí)現(xiàn)一個(gè)或多個(gè)像前文描述的那樣的作業(yè)處理流水線。下圖給出了Actor模型:


Channels

工作者之間不直接進(jìn)行通信。相反善延,它們?cè)诓煌耐ǖ乐邪l(fā)布自己的消息(事件)训唱。其他工作者們可以在這些通道上監(jiān)聽消息,發(fā)送者無需知道誰在監(jiān)聽挚冤。下圖給出了Channel模型:

channel模型對(duì)于來說似乎更加靈活况增。一個(gè)工作者無需知道誰在后面的流水線上處理作業(yè)。只需知道作業(yè)(或消息等)需要轉(zhuǎn)發(fā)給哪個(gè)通道训挡。通道上的監(jiān)聽者可以隨意訂閱或者取消訂閱澳骤,并不會(huì)影響向這個(gè)通道發(fā)送消息的工作者。這使得工作者之間具有松散的耦合澜薄。

3为肮、實(shí)現(xiàn)多線程方式

多線程實(shí)現(xiàn)方法有兩種:

  • 繼承Thread類
public class MyThread extends Thread {
   public void run(){
     System.out.println("MyThread running");
   }
}

//調(diào)用
MyThread myThread = new MyThread();
myTread.start();
  • 實(shí)現(xiàn)Runnble接口
public class MyRunnable implements Runnable {
   public void run(){
    System.out.println("MyRunnable running");
   }
}

//調(diào)用
Thread thread = new Thread(new MyRunnable());
thread.start();

實(shí)現(xiàn)Runnble接口比Thread類的優(yōu)勢:

  • 可以避免Java單繼承帶來的局限
  • 增強(qiáng)程序健壯性,能夠被多個(gè)線程共享肤京,代碼和數(shù)據(jù)是獨(dú)立的
  • 適合多個(gè)相同程序代碼的線程區(qū)處理同一資源

Thread中颊艳,start和run的區(qū)別:run是在當(dāng)前線程運(yùn)行,start是開辟新的線程運(yùn)行忘分!所以一般情況下使用的是start棋枕!
執(zhí)行完run()方法后,或在run()方法中return妒峦,線程便自然消亡重斑。

線程中斷

當(dāng)一個(gè)線程運(yùn)行時(shí),另一個(gè)線程可以調(diào)用對(duì)應(yīng)的 Thread 對(duì)象的 interrupt()方法來中斷它肯骇,該方法只是在目標(biāo)線程中設(shè)置一個(gè)標(biāo)志窥浪,表示它已經(jīng)被中斷,并立即返回笛丙。這里需要注意的是漾脂,如果只是單純的調(diào)用 interrupt()方法,線程并沒有實(shí)際被中斷胚鸯,會(huì)繼續(xù)往下執(zhí)行骨稿。

sleep()方法的實(shí)現(xiàn)檢查到休眠線程被中斷,它會(huì)相當(dāng)友好地終止線程蠢琳,并拋出 InterruptedException 異常啊终。

public class SleepInterrupt extends Object implements Runnable{  
    public void run(){  
        try{  
            System.out.println("in run() - about to sleep for 20 seconds");  
            Thread.sleep(20000);  
            System.out.println("in run() - woke up");  
        }catch(InterruptedException e){  
            System.out.println("in run() - interrupted while sleeping");  
            //處理完中斷異常后,返回到run()方法人口傲须,  
            //如果沒有return蓝牲,線程不會(huì)實(shí)際被中斷,它會(huì)繼續(xù)打印下面的信息  
            return;    
        }  
        System.out.println("in run() - leaving normally");  
    }  

    public static void main(String[] args) {  
        SleepInterrupt si = new SleepInterrupt();  
        Thread t = new Thread(si);  
        t.start();  
        //主線程休眠2秒泰讽,從而確保剛才啟動(dòng)的線程有機(jī)會(huì)執(zhí)行一段時(shí)間  
        try {  
            Thread.sleep(2000);   
        }catch(InterruptedException e){  
            e.printStackTrace();  
        }  
        System.out.println("in main() - interrupting other thread");  
        //中斷線程t  
        t.interrupt();  
        System.out.println("in main() - leaving");  
    }  
} 

如果將 catch 塊中的 return 語句注釋掉例衍,則線程在拋出異常后昔期,會(huì)繼續(xù)往下執(zhí)行,而不會(huì)被中斷佛玄,從而會(huì)打印出leaving normally信息硼一。

待決中斷

另外一種情況,如果線程在調(diào)用 sleep()方法前被中斷梦抢,那么該中斷稱為待決中斷般贼,它會(huì)在剛調(diào)用 sleep()方法時(shí),立即拋出 InterruptedException 異常奥吩。

public class PendingInterrupt extends Object {  
    public static void main(String[] args){  
        //如果輸入了參數(shù)哼蛆,則在mian線程中中斷當(dāng)前線程(亦即main線程)  
        if( args.length > 0 ){  
            Thread.currentThread().interrupt();  
        }   
        //獲取當(dāng)前時(shí)間  
        long startTime = System.currentTimeMillis();  
        try{  
            Thread.sleep(2000);  
            System.out.println("was NOT interrupted");  
        }catch(InterruptedException x){  
            System.out.println("was interrupted");  
        }  
        //計(jì)算中間代碼執(zhí)行的時(shí)間  
        System.out.println("elapsedTime=" + ( System.currentTimeMillis() - startTime));  
    }  
}

這種模式下,main 線程中斷它自身霞赫。除了將中斷標(biāo)志(它是 Thread 的內(nèi)部標(biāo)志)設(shè)置為 true 外腮介,沒有其他任何影響。線程被中斷了端衰,但 main 線程仍然運(yùn)行叠洗,main 線程繼續(xù)監(jiān)視實(shí)時(shí)時(shí)鐘,并進(jìn)入 try 塊旅东,一旦調(diào)用 sleep()方法灭抑,它就會(huì)注意到待決中斷的存在,并拋出 InterruptException玉锌。

中斷狀態(tài)判斷

  • isInterrupted()方法判斷是否中斷
  • Thread.interrupted()方法判斷中斷狀態(tài)

join & yield

join 方法用線程對(duì)象調(diào)用名挥,如果在一個(gè)線程 A 中調(diào)用另一個(gè)線程 B 的 join 方法,線程 A 將會(huì)等待線程 B 執(zhí)行完畢后再執(zhí)行主守。

yield 可以直接用 Thread 類調(diào)用,yield 讓出 CPU 執(zhí)行權(quán)給同等級(jí)的線程榄融,如果沒有相同級(jí)別的線程在等待 CPU 的執(zhí)行權(quán)参淫,則該線程繼續(xù)執(zhí)行。

守護(hù)線程

Java有兩類線程:UserThread(用戶線程)愧杯、Daemon Thread(守護(hù)線程)涎才。
用戶線程在前臺(tái),守護(hù)線程在后臺(tái)運(yùn)行链沼,為其他前臺(tái)線程提供服務(wù)茅诱。當(dāng)所有前臺(tái)線程都退出時(shí)错沃,守護(hù)線程就會(huì)退出。如果有前臺(tái)線程仍然存活棕兼,守護(hù)線程就不會(huì)退出。
守護(hù)線程并非只有虛擬機(jī)內(nèi)部提供抵乓,用戶可以使用Thread.setDaemon(true)方法設(shè)置為當(dāng)前線程為守護(hù)線程伴挚。

  • setDaemon(true)必須在調(diào)用的線程的start()方法之前設(shè)置靶衍,否則會(huì)拋出異常。
  • 在守護(hù)線程中產(chǎn)生的新線程也是守護(hù)線程

線程阻塞

線程在以下四種狀態(tài)下會(huì)產(chǎn)生阻塞:

  1. 執(zhí)行Thread.sleep()
  2. 當(dāng)線程遇見wait()語句茎芋,它會(huì)一直阻塞到接到通知notify()
  3. 線程阻塞與不同的I/O的方式有多種颅眶。例:InputStreamread方法,一直阻塞到從流中讀取一個(gè)字節(jié)的數(shù)據(jù)為知田弥。
  4. 線程阻塞等待獲取某個(gè)對(duì)象鎖的訪問權(quán)限涛酗。

4、線程安全

定義:當(dāng)多個(gè)線程訪問某個(gè)類時(shí)偷厦,這個(gè)類始終都能表現(xiàn)出正確的行為商叹,那么這個(gè)類就是線程安全的!

競態(tài)條件 & 臨界區(qū)

當(dāng)兩個(gè)線程競爭同一資源時(shí),如果對(duì)資源的訪問順序敏感沪哺,就稱存在競態(tài)條件沈自。
導(dǎo)致競態(tài)條件發(fā)生的代碼區(qū)稱作:臨界區(qū)。

下例中add()方法就是一個(gè)臨界區(qū),它會(huì)產(chǎn)生競態(tài)條件辜妓。在臨界區(qū)中使用適當(dāng)?shù)耐骄涂梢员苊飧倯B(tài)條件枯途。

public class Counter {
    protected long count = 0;
    public void add(long value){
        this.count = this.count + value;   
    }
}

數(shù)據(jù)安全

線程逃逸規(guī)則:如果一個(gè)資源的創(chuàng)建,使用籍滴,銷毀都在同一個(gè)線程內(nèi)完成酪夷,且永遠(yuǎn)不會(huì)脫離該線程的控制,則該資源的使用就是線程安全的孽惰。

屬性 描述 是否線程安全
局部變量 在棧中晚岭,不會(huì)被線程共享 線程安全
局部對(duì)象 引用所指的對(duì)象都存在共享堆中,對(duì)象不會(huì)被其它方法獲得勋功,也不會(huì)被非局部變量引用到 線程安全
對(duì)象成員 多個(gè)線程執(zhí)行讀操作坦报,或者每個(gè)線程的對(duì)象都相互獨(dú)立 線程安全
局部對(duì)象 對(duì)象會(huì)被其它方法獲得,或者被全局變量引用到 線程非安全
對(duì)象成員 存儲(chǔ)在堆上狂鞋。若多個(gè)線程同時(shí)更新同一個(gè)對(duì)象的同一個(gè)成員 線程非安全

線程安全

當(dāng)多個(gè)線程同時(shí)訪問同一個(gè)資源片择,并且其中的一個(gè)或者多個(gè)線程對(duì)這個(gè)資源進(jìn)行了寫操作,才會(huì)產(chǎn)生競態(tài)條件骚揍。多個(gè)線程同時(shí)讀同一個(gè)資源不會(huì)產(chǎn)生競態(tài)條件字管。

我們可以通過創(chuàng)建不可變的共享對(duì)象來保證對(duì)象在線程間共享時(shí)不會(huì)被修改,從而實(shí)現(xiàn)線程安全,如下所示:

public class ImmutableValue{
    private int value = 0;

    public ImmutableValue(int value){
        this.value = value;
    }

    public int getValue(){
        return this.value;
    }
}

如果非要對(duì)ImmutableValue進(jìn)行操作的話信不,可以創(chuàng)建新的實(shí)例進(jìn)行隔離:

public class ImmutableValue{
    private int value = 0;

    public ImmutableValue(int value){
        this.value = value;
    }

    public int getValue(){
        return this.value;
    }

    //創(chuàng)建一個(gè)新的實(shí)例
    public ImmutableValue add(int valueToAdd){
        return new ImmutableValue(this.value + valueToAdd);
    }
}

ImmutableValue可以看做是線程安全的嘲叔,但是如果別的類引用了ImmutableValue,就不能保證線程安全了抽活。如下所示:

public void Calculator{
    private ImmutableValue currentValue = null;

    public ImmutableValue getValue(){
        return currentValue;
    }

    public void setValue(ImmutableValue newValue){
        this.currentValue = newValue;
    }

    public void add(int newValue){
        this.currentValue = this.currentValue.add(newValue);
    }
}

即使Calculator類內(nèi)部使用了一個(gè)不可變對(duì)象硫戈,但Calculator類本身還是可變的,因此Calculator類不是線程安全的酌壕。換句話說:ImmutableValue類是線程安全的掏愁,但使用它的類不是歇由。

5、同步(synchronized)

當(dāng)多個(gè)線程訪問某個(gè)狀態(tài)變量果港,并且有線程執(zhí)行寫入操作時(shí)沦泌,必須采用同步機(jī)制來協(xié)同這些線程對(duì)變量的訪問。

Java的主要同步機(jī)制有:

  1. synchronized關(guān)鍵字
  2. volatile類型變量
  3. 顯示鎖
  4. 原子變量

無論是同步方法辛掠,還是同步塊都是只針對(duì)同一個(gè)對(duì)象的多線程而言的谢谦,只有同一個(gè)對(duì)象產(chǎn)生的多線程,才會(huì)考慮到同步方法或者是同步塊萝衩。

實(shí)例方法

Java實(shí)例方法同步是同步在對(duì)象上回挽。這樣,每個(gè)方法同步都同步在方法所屬的實(shí)例猩谊。只有一個(gè)線程能夠在實(shí)例方法同步塊中運(yùn)行千劈。如果有多個(gè)實(shí)例存在,那么一個(gè)線程一次可以在一個(gè)實(shí)例同步塊中執(zhí)行操作牌捷。一個(gè)實(shí)例一個(gè)線程墙牌。

 public synchronized void add(int value){
    this.count += value;
 }

靜態(tài)方法同步

靜態(tài)方法的同步是指同步在該方法所在的類對(duì)象上。因?yàn)樵贘ava虛擬機(jī)中一個(gè)類只能對(duì)應(yīng)一個(gè)類對(duì)象暗甥,所以同時(shí)只允許一個(gè)線程執(zhí)行同一個(gè)類中的靜態(tài)同步方法喜滨。

對(duì)于不同類中的靜態(tài)同步方法,一個(gè)線程可以執(zhí)行每個(gè)類中的靜態(tài)同步方法而無需等待撤防。不管類中的那個(gè)靜態(tài)同步方法是否被調(diào)用虽风,一個(gè)類只能由一個(gè)線程同時(shí)執(zhí)行。

public static synchronized void add(int value){
    count += value;
}

實(shí)例方法中的同步塊

有時(shí)你不需要同步整個(gè)方法寄月,而是同步方法中的一部分辜膝。

public void add(int value){
    synchronized(this){
       this.count += value;
    }
}

示例使用Java同步塊構(gòu)造器來標(biāo)記一塊代碼是同步的。該代碼在執(zhí)行時(shí)和同步方法一樣漾肮。在上例中内舟,使用了“this”,即為調(diào)用add方法的實(shí)例本身初橘。在同步構(gòu)造器中用括號(hào)括起來的對(duì)象叫做監(jiān)視器對(duì)象。

靜態(tài)方法中的同步塊

和上面類似充岛,下面是兩個(gè)靜態(tài)方法同步的例子保檐。這些方法同步在該方法所屬的類對(duì)象上。

public class MyClass {
    public static synchronized void log1(String msg1, String msg2){
       log.writeln(msg1);
       log.writeln(msg2);
    }

    public static void log2(String msg1, String msg2){
       synchronized(MyClass.class){
          log.writeln(msg1);
          log.writeln(msg2);
       }
    }
  }

這兩個(gè)方法不允許同時(shí)被線程訪問崔梗。
如果第二個(gè)同步塊不是同步在MyClass.class這個(gè)對(duì)象上夜只。那么這兩個(gè)方法可以同時(shí)被線程訪問。

6蒜魄、線程通信

線程通信的目標(biāo)是使線程間能夠互相發(fā)送信號(hào)扔亥。另一方面场躯,線程通信使線程能夠等待其他線程的信號(hào)。

通過共享對(duì)象通信

線程間發(fā)送信號(hào)的一個(gè)簡單方式是在共享對(duì)象的變量里設(shè)置信號(hào)值旅挤。

public class MySignal{
  protected boolean hasDataToProcess = false;

  public synchronized boolean hasDataToProcess(){
    return this.hasDataToProcess;
  }

  public synchronized void setHasDataToProcess(boolean hasData){
    this.hasDataToProcess = hasData;
  }
}

線程A在一個(gè)同步塊里設(shè)置boolean型成員變量hasDataToProcess為true踢关,線程B也在同步塊里讀取hasDataToProcess這個(gè)成員變量。
線程A和B必須獲得指向一個(gè)MySignal共享實(shí)例的引用粘茄,以便進(jìn)行通信签舞。如果它們持有的引用指向不同的MySingal實(shí)例,那么彼此將不能檢測到對(duì)方的信號(hào)柒瓣。

忙等待(Busy Wait)

線程B運(yùn)行在一個(gè)循環(huán)里,等待線程A的一個(gè)可執(zhí)行的信號(hào)儒搭。

protected MySignal sharedSignal = ...

...
while(!sharedSignal.hasDataToProcess()){
   //do nothing... busy waiting
}

wait(),notify()和notifyAll()

除非忙等待的時(shí)間特別短,否則會(huì)浪費(fèi)CPU資源芙贫。合理的做法:讓等待線程進(jìn)入睡眠或者非運(yùn)行狀態(tài)搂鲫,直到它接收到它等待的信號(hào)。

java.lang.Object 類定義了三個(gè)方法磺平,wait()魂仍、notify()和notifyAll()來實(shí)現(xiàn)這個(gè)等待機(jī)制。

一個(gè)線程一旦調(diào)用了任意對(duì)象的wait()方法褪秀,就會(huì)變?yōu)榉沁\(yùn)行狀態(tài)蓄诽,直到另一個(gè)線程調(diào)用了同一個(gè)對(duì)象的notify()方法。

為了調(diào)用wait()或者notify()媒吗,線程必須先獲得那個(gè)對(duì)象的鎖仑氛。也就是說,線程必須在同步塊里調(diào)用wait()或者notify()闸英。

在wait()/notify()機(jī)制中锯岖,不要使用全局對(duì)象,字符串常量等甫何。應(yīng)該使用對(duì)應(yīng)唯一的對(duì)象

public class MonitorObject{
}

public class MyWaitNotify{

  MonitorObject myMonitorObject = new MonitorObject();

  public void doWait(){
    synchronized(myMonitorObject){
      try{
        myMonitorObject.wait();
      } catch(InterruptedException e){...}
    }
  }

  public void doNotify(){
    synchronized(myMonitorObject){
      myMonitorObject.notify();
    }
  }
}

不管是等待線程還是喚醒線程都在同步塊里調(diào)用wait()和notify()出吹。這是強(qiáng)制性的!一個(gè)線程如果沒有持有對(duì)象鎖辙喂,將不能調(diào)用wait()捶牢,notify()或者notifyAll()。否則巍耗,會(huì)拋出IllegalMonitorStateException異常秋麸。

一旦線程調(diào)用了wait()方法,它就釋放了所持有的監(jiān)視器對(duì)象上的鎖炬太。這將允許其他線程也可以調(diào)用wait()或者notify()灸蟆。

被喚醒的線程必須重新獲得監(jiān)視器對(duì)象的鎖,才可以退出wait()的方法調(diào)用亲族,因?yàn)閣ait方法調(diào)用運(yùn)行在同步塊里面炒考。如果多個(gè)線程被notifyAll()喚醒可缚,那么在同一時(shí)刻將只有一個(gè)線程可以退出wait()方法,因?yàn)槊總€(gè)線程在退出wait()前必須獲得監(jiān)視器對(duì)象的鎖斋枢。

丟失信號(hào)

notify()和notifyAll()方法不會(huì)保存調(diào)用它們的方法帘靡,如果方法被調(diào)用時(shí),沒有線程處于等待狀態(tài)杏慰。通知信號(hào)過后便丟棄了测柠。因此,如果一個(gè)線程先于被通知線程調(diào)用wait()前調(diào)用了notify()缘滥,等待的線程將錯(cuò)過這個(gè)信號(hào)轰胁。在某些情況下,這可能使線程錯(cuò)過了喚醒信號(hào)朝扼,永遠(yuǎn)在等待不再醒來赃阀。

為了避免丟失信號(hào),必須把它們保存在信號(hào)類里擎颖。在MyWaitNotify的例子中榛斯,通知信號(hào)應(yīng)被存儲(chǔ)在MyWaitNotify實(shí)例的一個(gè)成員變量里。

public class MyWaitNotify2{

  MonitorObject myMonitorObject = new MonitorObject();
  boolean wasSignalled = false;

  public void doWait(){
    synchronized(myMonitorObject){
      if(!wasSignalled){
        try{
          myMonitorObject.wait();
         } catch(InterruptedException e){...}
      }
      //clear signal and continue running.
      wasSignalled = false;
    }
  }

  public void doNotify(){
    synchronized(myMonitorObject){
      wasSignalled = true;
      myMonitorObject.notify();
    }
  }
}

在上述例子中搂捧,doNotify()方法在調(diào)用notify()前把wasSignalled變量設(shè)為true驮俗。同時(shí),留意doWait()方法在調(diào)用wait()前會(huì)檢查wasSignalled變量允跑。

為了避免信號(hào)丟失王凑,用一個(gè)變量來保存是否被通知過。在notify前聋丝,設(shè)置自己已經(jīng)被通知過索烹。在wait后,設(shè)置自己沒有被通知過弱睦,需要等待通知百姓。。

假喚醒

線程有可能在沒有調(diào)用過notify()和notifyAll()的情況下醒來况木。這就是所謂的假喚醒(spurious wakeups)垒拢。等待線程即使沒有收到正確的信號(hào),也能夠執(zhí)行后續(xù)的操作火惊。

為了防止假喚醒子库,保存信號(hào)的成員變量將在一個(gè)while循環(huán)里接受檢查,而不是在if表達(dá)式里矗晃。這樣的一個(gè)while循環(huán)叫做自旋鎖。

public class MyWaitNotify3{

  MonitorObject myMonitorObject = new MonitorObject();
  boolean wasSignalled = false;

  public void doWait(){
    synchronized(myMonitorObject){
      while(!wasSignalled){
        try{
          myMonitorObject.wait();
         } catch(InterruptedException e){...}
      }
      //clear signal and continue running.
      wasSignalled = false;
    }
  }

  public void doNotify(){
    synchronized(myMonitorObject){
      wasSignalled = true;
      myMonitorObject.notify();
    }
  }
}

如果等待線程沒有收到信號(hào)就喚醒宴倍,wasSignalled變量將變?yōu)閒alse,while循環(huán)會(huì)再執(zhí)行一次张症,促使醒來的線程回到等待狀態(tài)仓技。

目前的JVM實(shí)現(xiàn)自旋會(huì)消耗CPU,如果長時(shí)間不調(diào)用doNotify方法俗他,doWait方法會(huì)一直自旋脖捻,CPU會(huì)消耗太大。

7兆衅、TheadLocal

ThreadLocal類創(chuàng)建的變量只被同一個(gè)線程進(jìn)行讀和寫操作地沮。因此,盡管有兩個(gè)線程同時(shí)執(zhí)行一段相同的代碼羡亩,而且這段代碼又有一個(gè)指向同一個(gè)ThreadLocal變量的引用摩疑,但是這兩個(gè)線程依然不能看到彼此的ThreadLocal變量域。

//創(chuàng)建一個(gè)ThreadLocal變量:每個(gè)線程僅需要實(shí)例化一次即可畏铆。
//每個(gè)線程只能看到私有的ThreadLocal實(shí)例,不同的線程在給ThreadLocal對(duì)象設(shè)置不同的值雷袋,也不能看到彼此的修改。
private ThreadLocal myThreadLocal = new ThreadLocal();

//設(shè)置辞居、獲取數(shù)據(jù)
myThreadLocal.set("A thread local value");
String threadLocalValue = (String) myThreadLocal.get();

//創(chuàng)建泛型對(duì)象
private ThreadLocal myThreadLocal1 = new ThreadLocal<String>();

myThreadLocal1.set("Hello ThreadLocal");
String threadLocalValues = myThreadLocal.get();

InheritableThreadLocal類是ThreadLocal的子類楷怒。為了解決ThreadLocal實(shí)例內(nèi)部每個(gè)線程都只能看到自己的私有值,所以InheritableThreadLocal允許一個(gè)線程創(chuàng)建的所有子線程訪問其父線程的值瓦灶。


引用

1鸠删、并發(fā)編程網(wǎng)-Java并發(fā)性和多線程
2、蘭亭風(fēng)雨專欄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贼陶,一起剝皮案震驚了整個(gè)濱河市刃泡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌每界,老刑警劉巖捅僵,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異眨层,居然都是意外死亡庙楚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門趴樱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來馒闷,“玉大人,你說我怎么就攤上這事叁征∧烧耍” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵捺疼,是天一觀的道長疏虫。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么卧秘? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任呢袱,我火速辦了婚禮,結(jié)果婚禮上翅敌,老公的妹妹穿的比我還像新娘羞福。我一直安慰自己,他們只是感情好蚯涮,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布治专。 她就那樣靜靜地躺著,像睡著了一般遭顶。 火紅的嫁衣襯著肌膚如雪张峰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天液肌,我揣著相機(jī)與錄音挟炬,去河邊找鬼。 笑死嗦哆,一個(gè)胖子當(dāng)著我的面吹牛谤祖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播老速,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼粥喜,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了橘券?” 一聲冷哼從身側(cè)響起额湘,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎旁舰,沒想到半個(gè)月后锋华,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡箭窜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年毯焕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片磺樱。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纳猫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出竹捉,到底是詐尸還是另有隱情芜辕,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布块差,位于F島的核電站侵续,受9級(jí)特大地震影響倔丈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜询兴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一乃沙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诗舰,春花似錦、人聲如沸训裆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽边琉。三九已至属百,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間变姨,已是汗流浹背族扰。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留定欧,地道東北人渔呵。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像砍鸠,于是被迫代替她去往敵國和親扩氢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 本文主要講了java中多線程的使用方法爷辱、線程同步录豺、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法饭弓、概述等双饥。 首先講...
    李欣陽閱讀 2,444評(píng)論 1 15
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,952評(píng)論 1 18
  • 寫在前面的話: 這篇博客是我從這里“轉(zhuǎn)載”的,為什么轉(zhuǎn)載兩個(gè)字加“”呢弟断?因?yàn)檫@絕不是簡單的復(fù)制粘貼咏花,我花了五六個(gè)小...
    SmartSean閱讀 4,717評(píng)論 12 45
  • 該文章轉(zhuǎn)自:http://blog.csdn.net/evankaka/article/details/44153...
    加來依藍(lán)閱讀 7,337評(píng)論 3 87
  • 幸福路人春風(fēng)20171110第159天 陪孩子的過程真的是不容易的。今天女兒出現(xiàn)狀況了夫嗓,她上的英語輔導(dǎo)班迟螺,上節(jié)課由...
    春風(fēng)7861閱讀 343評(píng)論 0 0