秒懂Java多線程

版權(quán)申明】非商業(yè)目的可自由轉(zhuǎn)載
博文地址:http://www.reibang.com/p/25675e583943
出自:shusheng007

前言

這個(gè)話題一般比較大较店,如果往深了研究學(xué)問(wèn)可大了伦糯,不僅涉及到操作系統(tǒng)知識(shí)還會(huì)涉及計(jì)算機(jī)硬件的知識(shí)概疆,本文將著眼于應(yīng)用層面行文。有的同學(xué)要說(shuō)了:“講那么多干什么板惑,還不是因?yàn)樽约翰恕保抑荒苷f(shuō):“被你看穿了枫攀,呵呵”。

概述

追求工作效率是人類社會(huì)能夠迅速向前發(fā)展的動(dòng)力株茶,例如老王公司的軟件部門(mén)有大把資金大把項(xiàng)目来涨,但是只有一個(gè)碼農(nóng)小明,而小明計(jì)劃一個(gè)一個(gè)的把項(xiàng)目做完启盛。老王就急了蹦掐,我這分分鐘幾百萬(wàn)的生意,你這做到猴年馬月呢僵闯,于是就又雇了一批碼農(nóng)卧抗,將各個(gè)項(xiàng)目同時(shí)啟動(dòng)。那么我們可以把每一個(gè)碼農(nóng)看成一個(gè)線程(Thread)鳖粟,這樣就形成了多任務(wù)并發(fā)執(zhí)行了(其實(shí)這個(gè)例子已經(jīng)是并行執(zhí)行了)社裆。

那么由人設(shè)計(jì)的計(jì)算機(jī)操作系統(tǒng)也不例外,它也會(huì)想盡一切辦法提高任務(wù)執(zhí)行效率的向图,于是乎多線程應(yīng)用而生浦马。

進(jìn)程與線程

面過(guò)試的都知道,至于標(biāo)準(zhǔn)答案大家可以網(wǎng)上搜索一下张漂。你只要知道進(jìn)程面向操作系統(tǒng)晶默,線程面向進(jìn)程。進(jìn)程是操作系統(tǒng)實(shí)現(xiàn)多任務(wù)的手段航攒,多個(gè)進(jìn)程會(huì)互相隔離,擁有自己獨(dú)立的地址空間與資源漠畜。而線程存在于進(jìn)程中,隔離不是很?chē)?yán)重憔狞,可以共享同一個(gè)進(jìn)程中的內(nèi)存數(shù)據(jù)。

多線程的作用

  • 可以充分利用多CPU的硬件資源瘾敢,提高任務(wù)執(zhí)行效率。
  • 可以執(zhí)行后臺(tái)任務(wù)簇抵,當(dāng)使用瀏覽器下載一部小電影的同時(shí),你可以去瀏覽下性感美女的圖片碟摆。
  • 提高GUI程序的用戶體驗(yàn),你也不希望在手機(jī)上點(diǎn)擊了一個(gè)下載按鈕后典蜕,App就卡死在那里了罗洗。
  • 等等...

Java中如何使用多線程

Java 對(duì)多線程的支持非常完善,Java使用Thread類來(lái)表示線程伙菜,下面的使用均與此類相關(guān)。

繼承Thread類創(chuàng)建線程

繼承Thread類厢洞,重寫(xiě)其run()方法即可仇让。啟動(dòng)此線程時(shí)典奉,只需要new MyThread().start();即可躺翻。

public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        System.out.println("線程名稱:"+getName());
    }
}

使用Runnable創(chuàng)建線程

從源碼可知Thread存在這樣一個(gè)構(gòu)造函數(shù) public Thread(Runnable target) ,因而我們可以使用實(shí)現(xiàn)Runnable接口的方式創(chuàng)建線程卫玖。

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("線程名稱:"+Thread.currentThread().getName());
    }
}).start();

由于Runnable接口是一個(gè)函數(shù)接口公你,所以我們可以使用Lambda表達(dá)式來(lái)實(shí)現(xiàn),如下所示:

new Thread(() -> System.out.println("線程名稱:"+Thread.currentThread().getName())).start();

通過(guò)這種方式多個(gè)線程可以共享線程執(zhí)行體假瞬,但是線程執(zhí)行結(jié)果無(wú)法獲得陕靠,run()方法沒(méi)有返回值。

使用Callable和Future創(chuàng)建線程

通過(guò)這種方式創(chuàng)建的線程可以有返回值脱茉,此處使用Callable 作為線程的執(zhí)行體剪芥,其包含一個(gè)擁有返回值的方法call()

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

上面提到,Thread的構(gòu)造方法需要一個(gè)Runnable類型的參數(shù)琴许,所以不可以直接使用Callable來(lái)創(chuàng)建線程税肪。Java提供了一個(gè)叫Futurer的接口來(lái)表示Callbale接口中call()方法的返回值。還為其提供了一個(gè)實(shí)現(xiàn)類FutureTask榜田,此類實(shí)現(xiàn)了FutureRunnable接口益兄,這樣FutureTask類就可以作為參數(shù)構(gòu)建線程了。

talk is cheap 箭券,show me the code.

  private static void startThread()
  {
      //第一步:創(chuàng)建callable實(shí)現(xiàn)類
      Callable<String> c=new Callable<String>() {
          @Override
          public String call() throws Exception {
              //經(jīng)過(guò)大量耗時(shí)運(yùn)算得出結(jié)論
              return "總有刁民想害朕";
          }
      };
      //第二步:以c作為參數(shù)創(chuàng)建FutureTask實(shí)例ft
      FutureTask<String>ft=new FutureTask<String>(c);
      //第三步:以ft為參數(shù)啟動(dòng)線程
      new Thread(ft).start();
      //第四步:獲取執(zhí)行結(jié)果净捅,get()方法是一個(gè)阻塞方法。
      try {
          System.out.println("錦衣衛(wèi)調(diào)查謀反結(jié)論:"+ft.get());
      } catch (InterruptedException e) {
          e.printStackTrace();
      } catch (ExecutionException e) {
          e.printStackTrace();
      }
  }

輸出結(jié)果:錦衣衛(wèi)調(diào)查謀反結(jié)論:總有刁民想害朕

如何使用在代碼注釋中已經(jīng)寫(xiě)的非常清楚了辩块,如果你仍然看不懂蛔六,說(shuō)明你目前水平太差,不適合看這篇文章废亭!

線程的同步

談到多線程古今,首先繞不過(guò)的話題就是線程同步。因?yàn)槎嗑€程會(huì)對(duì)共享資源狀態(tài)產(chǎn)生競(jìng)態(tài)條件Race condition)滔以,競(jìng)態(tài)條件是指輸出依賴不可控事件發(fā)生的順序或者時(shí)間的行為捉腥,當(dāng)這些不可控事件沒(méi)有按照預(yù)期發(fā)生時(shí),就會(huì)產(chǎn)生bug你画。對(duì)應(yīng)到編程中就是指多個(gè)線程如果沒(méi)有按照預(yù)期的順序或者時(shí)間來(lái)操作共享狀態(tài)時(shí)就會(huì)產(chǎn)生bug抵碟。

假設(shè)我們現(xiàn)在使用兩個(gè)線程Thread1Thread2來(lái)并發(fā)使一個(gè)整數(shù)自增桃漾,我們期望是兩個(gè)線程按照如下的順序執(zhí)行得到正確值2:

1.jpg

而實(shí)際情況是兩個(gè)沒(méi)有加鎖或者同步的線程來(lái)并發(fā)做這件事情的話,很有可能執(zhí)行順序如下圖所示:


2.jpg

很明顯撬统,第二種情況得到了錯(cuò)誤的結(jié)果1敦迄,這種情況之所以發(fā)生就是因?yàn)檎麛?shù)自增操作不是排他Mutual exclusivity容)的罚屋,在發(fā)生競(jìng)態(tài)條件時(shí)出了錯(cuò)誤。解決上述問(wèn)題就需要線程的同步技術(shù)撕彤。

使用synchronized關(guān)鍵字

同步代碼塊

我們可以使用同步代碼塊將需要同步的資源操作保護(hù)起來(lái)羹铅,如下代碼所示职员。其中obj稱作同步監(jiān)視器跛溉,通常推薦使用可能被并發(fā)訪問(wèn)的共享資源充當(dāng)。

   synchronized (obj)
   {
       ...
   }

同步方法

我們也可以使用同步方法將需要同步的操作置于此方法中蛛蒙,如下代碼所示牵祟。此實(shí)例方法的同步監(jiān)視器就是調(diào)用此方法的實(shí)例對(duì)象this诺苹。如果是靜態(tài)同步方法雹拄,那么同步監(jiān)視器就是類本身滓玖。

    private synchronized void synMethod()
    {
        ...
    }

使用同步鎖(Lock)

Java5提供了另一種同步代碼的方式,鎖(Lock).我們?cè)趯W(xué)習(xí)編程的過(guò)程中翩肌,只要發(fā)現(xiàn)一個(gè)問(wèn)題以前已經(jīng)有一套解決方案念祭,突然在新版本中又提供了另一套解決方案,那么我們立刻可以肯定:在實(shí)際開(kāi)發(fā)中第一套解決方案對(duì)于解決某些特殊場(chǎng)景下的問(wèn)題時(shí)遇到了困難隶糕,才引入第二套解決方案枚驻,第二套解決方案大部分情況下不是用來(lái)完全替換第一套解決方案的测秸,而是其補(bǔ)充和增強(qiáng)灾常。像Lock就是synchronized 的補(bǔ)充和增強(qiáng)铃拇,在日常大部分的開(kāi)發(fā)場(chǎng)景下synchronized 已經(jīng)足夠了慷荔,Lock在特殊場(chǎng)景下才會(huì)使用。

synchronized其實(shí)獲取的是每個(gè)object都有的隱式監(jiān)視器鎖(implicit monitor lock )贷岸,其要求程序獲取和釋放鎖的操作都限定在一個(gè)塊結(jié)構(gòu)里偿警,就是說(shuō)其獲取鎖和釋放鎖這兩個(gè)操作不是很靈活螟蒸,當(dāng)遇到需要這兩個(gè)操作不在同一個(gè)塊結(jié)構(gòu)的場(chǎng)景就無(wú)法適應(yīng)了崩掘。這是引入Lock的主要原因苞慢,當(dāng)然Locksynchronized的功能更加豐富,例如使用tryLock()方法嘗試獲取鎖,如果當(dāng)前鎖沒(méi)有釋放绍赛,則返回false.例如lockInterruptibly()方法嘗試獲取鎖惹资,但是如果當(dāng)前鎖沒(méi)有釋放,其轉(zhuǎn)入阻塞狀態(tài)猴誊,剛好此時(shí)別的線程中斷了此線程懈叹,則會(huì)拋出異常分扎,不再嘗試獲取鎖畏吓。

下面是官方舉出的一個(gè)需要使用lock的場(chǎng)景:

For example, some algorithms for traversing concurrently accessed data structures require the use of * "hand-over-hand" or "chain locking": you acquire the lock of node A, then node B, then release A and acquire
C, then release B and acquire D and so on. Implementations of the {@code Lock} interface enable the use of such techniques by allowing a lock to be acquired and released in different scopes, and allowing multiple locks to be acquired and released in any order.

鎖有很多實(shí)現(xiàn)類菲饼,我們這里主要關(guān)注一個(gè)ReentrantLock的實(shí)現(xiàn)類,使用代碼如下

   private final ReentrantLock lock=new ReentrantLock();
   private void m()
   {
       lock.lock();
       try {
           ...
           
       }catch (Exception e)
       {
           e.printStackTrace();
       }finally {
           lock.unlock();
       }
   }

線程的生命周期

線程的生命周期共有5個(gè)狀態(tài):新建(New)镐确、就緒(Runnable)源葫、運(yùn)行(Running)息堂、阻塞(Blocked)和死亡(Dead)芭届,他們的關(guān)系可以看下面的一張圖。
[圖片上傳失敗...(image-ccc912-1530440208607)]

具體解釋如下:

  1. 新建狀態(tài)(New): 線程對(duì)象被創(chuàng)建后持隧,就進(jìn)入了新建狀態(tài)逃片。
  2. 就緒狀態(tài)(Runnable): 也被稱為“可執(zhí)行狀態(tài)”。線程對(duì)象被創(chuàng)建后調(diào)用start()方法啟動(dòng)線程后其處于就緒狀態(tài)呀狼,隨時(shí)可能被CPU調(diào)度執(zhí)行。
  3. 運(yùn)行狀態(tài)(Running) : 線程獲取CPU權(quán)限進(jìn)行執(zhí)行绝编。需要注意的是十饥,線程只能從就緒狀態(tài)進(jìn)入到運(yùn)行狀態(tài)。
  4. 阻塞狀態(tài)(Blocked) : 阻塞狀態(tài)是線程因?yàn)槟撤N原因放棄CPU使用權(quán)逗堵,暫時(shí)停止運(yùn)行蜒秤,直到線程進(jìn)入就緒狀態(tài)亚斋,才有機(jī)會(huì)轉(zhuǎn)到運(yùn)行狀態(tài)。阻塞的情況分三種:
    (1) 等待阻塞 -- 通過(guò)調(diào)用線程的wait()方法伞访,讓線程等待某工作的完成轰驳。
    (2) 同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因?yàn)殒i被其它線程所占用)级解,它會(huì)進(jìn)入同步阻塞狀態(tài)。
    (3) 其他阻塞 -- 通過(guò)調(diào)用線程的sleep()或join()或發(fā)出了I/O請(qǐng)求時(shí)抡爹,線程會(huì)進(jìn)入到阻塞狀態(tài)芒划。當(dāng)sleep()狀態(tài)超時(shí)民逼、join()等 待線程終止或者超時(shí)、或者I/O處理完畢時(shí)笑诅,線程重新轉(zhuǎn)入就緒狀態(tài)。
  5. 死亡狀態(tài)(Dead) : 線程執(zhí)行完了或者因異常退出了run()方法弦叶,該線程結(jié)束生命周期伤哺。
    具體可以參考此博文

線程的控制

既然程序中存在多個(gè)線程者祖,那么我們就需要對(duì)多個(gè)線程執(zhí)行一些控制。

線程等待(Join)

Thread中提供了一個(gè)join()方法桃序,例如有兩個(gè)線程AB媒熊,在A的執(zhí)行過(guò)程中調(diào)用了Bjoin()方法芦鳍,那么A線程就會(huì)被阻塞柠衅,直到B線程執(zhí)行完畢菲宴。

線程睡眠(sleep)

這個(gè)大家一定不陌生喝峦,Thread.sleep(3*1000)使線程從運(yùn)行狀態(tài)進(jìn)入阻塞狀態(tài)3秒,此期間線程就不會(huì)被CPU調(diào)度執(zhí)行眉踱。

線程讓步(yield)

當(dāng)我們想讓線程調(diào)度器立刻做一次新的線程調(diào)度時(shí)霜威,可以調(diào)用當(dāng)前執(zhí)行線程的yield()方法叁执,此方法會(huì)使調(diào)用線程立刻進(jìn)入就緒狀態(tài)谈宛,線程調(diào)度器開(kāi)始一次新的線程調(diào)度吆录。此時(shí)線程優(yōu)先級(jí)就起作用了恢筝,線程調(diào)度器可定是先調(diào)度優(yōu)先級(jí)高的線程執(zhí)行撬槽。

例如有AB兩個(gè)線程侄柔,A的線程優(yōu)先級(jí)小于等于B線程暂题,那么當(dāng)調(diào)用A.yield()后薪者,B線程就會(huì)被調(diào)度執(zhí)行。如果A線程的優(yōu)先級(jí)大于B線程攻人,那么即使調(diào)用A.yield()后贝椿,B線程也得不到執(zhí)行,線程調(diào)度器仍然會(huì)再次調(diào)度線程A來(lái)執(zhí)行瑟蜈。

后臺(tái)線程

Java中有一類線程叫后臺(tái)線程(Daemon Thread),也叫守護(hù)線程宪躯,使用setDaemon(boolean b)設(shè)置一個(gè)線程是否為后臺(tái)線程访雪。這類線程有一個(gè)特點(diǎn),就是當(dāng)所有前臺(tái)線程都死亡后坝橡,后臺(tái)線程自動(dòng)死亡精置。

線程的通信

未完待續(xù)

線程池

參考Java8 并發(fā)教程之Thread與Executors

總結(jié)

未完待續(xù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末番宁,一起剝皮案震驚了整個(gè)濱河市蝶押,隨后出現(xiàn)的幾起案子火欧,更是在濱河造成了極大的恐慌布隔,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件招刨,死亡現(xiàn)場(chǎng)離奇詭異沉眶,居然都是意外死亡杉适,警方通過(guò)查閱死者的電腦和手機(jī)猿推,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)蹬叭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)秽五,“玉大人坦喘,你說(shuō)我怎么就攤上這事西设〈鹋螅” “怎么了绿映?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵叉弦,是天一觀的道長(zhǎng)淹冰。 經(jīng)常有香客問(wèn)我,道長(zhǎng)樱拴,這世上最難降的妖魔是什么柠衍? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮晶乔,結(jié)果婚禮上珍坊,老公的妹妹穿的比我還像新娘。我一直安慰自己正罢,他們只是感情好阵漏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著翻具,像睡著了一般。 火紅的嫁衣襯著肌膚如雪裆泳。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天运提,我揣著相機(jī)與錄音糙捺,去河邊找鬼竟痰。 笑死铅檩,一個(gè)胖子當(dāng)著我的面吹牛兔沃,可吹牛的內(nèi)容都是我干的怕吴。 我是一名探鬼主播硼啤,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼扯旷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼逼肯!你這毒婦竟也來(lái)了缺菌?” 一聲冷哼從身側(cè)響起耿战,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤纤泵,失蹤者是張志新(化名)和其女友劉穎肉渴,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡儒拂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了统倒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤琢岩,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布泳桦,位于F島的核電站完疫,受9級(jí)特大地震影響饰迹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赂摆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一喷楣、第九天 我趴在偏房一處隱蔽的房頂上張望曲伊。 院中可真熱鬧懈糯,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春轩性,著一層夾襖步出監(jiān)牢的瞬間铅祸,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工摇零, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人敬飒。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親闪金。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353