Java 多線程編程

Java 多線程編程

一個(gè)多線程程序包含兩個(gè)或多個(gè)能并發(fā)運(yùn)行的部分

程序的每一部分都稱作一個(gè)線程耿币,并且每個(gè)線程定義了一個(gè)獨(dú)立的執(zhí)行路徑

多線程是多任務(wù)的一種特別的形式禽额,但多線程使用了更小的資源開(kāi)銷

多線程能滿足程序員編寫高效率的程序來(lái)達(dá)到充分利用 CPU 的目的

Java 給多線程編程提供了內(nèi)置的支持

一個(gè)線程的生命周期

線程是一個(gè)動(dòng)態(tài)執(zhí)行的過(guò)程誉裆,它也有一個(gè)從產(chǎn)生到死亡的過(guò)程

下圖顯示了一個(gè)線程完整的生命周期

image
  • 新建狀態(tài)

    使用 new 關(guān)鍵字和 Thread 類或其子類建立一個(gè)線程對(duì)象后朴摊,該線程對(duì)象就處于新建狀態(tài)

    它保持這個(gè)狀態(tài)直到程序 start() 這個(gè)線程

  • 就緒狀態(tài)

    當(dāng)線程對(duì)象調(diào)用了start()方法之后媚狰,該線程就進(jìn)入就緒狀態(tài)

    就緒狀態(tài)的線程處于就緒隊(duì)列中沪摄,要等待 JVM 里線程調(diào)度器的調(diào)度

  • 運(yùn)行狀態(tài)

    如果就緒狀態(tài)的線程獲取 CPU 資源溪胶,就可以執(zhí)行 run() 搂擦,此時(shí)線程便處于運(yùn)行狀態(tài)

    處于運(yùn)行狀態(tài)的線程最為復(fù)雜,它可以變?yōu)樽枞麪顟B(tài)哗脖、就緒狀態(tài)和死亡狀態(tài)

  • 阻塞狀態(tài)

    如果一個(gè)線程執(zhí)行了 sleep ( 睡眠 ) 瀑踢、suspend ( 掛起 ) 等方法,失去所占用資源之后才避,該線程就從運(yùn)行狀態(tài)進(jìn)入阻塞狀態(tài)

    在睡眠時(shí)間已到或獲得設(shè)備資源后可以重新進(jìn)入就緒狀態(tài)

    可以分為三種

    • 等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行 wait() 方法橱夭,使線程進(jìn)入到等待阻塞狀態(tài)

    • 同步阻塞:線程在獲取 synchronized 同步鎖失敗(因?yàn)橥芥i被其他線程占用)

    • 其他阻塞:通過(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 處理完畢楞遏,線程重新轉(zhuǎn)入就緒狀態(tài)

  • 死亡狀態(tài)

    一個(gè)運(yùn)行狀態(tài)的線程完成任務(wù)或者其他終止條件發(fā)生時(shí)茬暇,該線程就切換到終止?fàn)顟B(tài)

線程的優(yōu)先級(jí)

每一個(gè) Java 線程都有一個(gè)優(yōu)先級(jí),這樣有助于操作系統(tǒng)確定線程的調(diào)度順序

Java 線程的優(yōu)先級(jí)是一個(gè)整數(shù)寡喝,其取值范圍是 1 ( Thread.MIN_PRIORITY ) - 10 ( Thread.MAX_PRIORITY )

默認(rèn)情況下糙俗,每一個(gè)線程都會(huì)分配一個(gè)優(yōu)先級(jí) NORM_PRIORITY ( 5 )

具有較高優(yōu)先級(jí)的線程對(duì)程序更重要,并且應(yīng)該在低優(yōu)先級(jí)的線程之前分配處理器資源

但是预鬓,線程優(yōu)先級(jí)不能保證線程執(zhí)行的順序巧骚,而且非常依賴于平臺(tái)

創(chuàng)建一個(gè)線程

Java 提供了三種創(chuàng)建線程的方法

  1. 通過(guò)實(shí)現(xiàn) Runnable 接口
  2. 通過(guò)繼承 Thread 類本身
  3. 通過(guò) Callable 和 Future 創(chuàng)建線程

通過(guò)實(shí)現(xiàn) Runnable 接口來(lái)創(chuàng)建線程

創(chuàng)建一個(gè)線程,最簡(jiǎn)單的方法是創(chuàng)建一個(gè)實(shí)現(xiàn) Runnable 接口的類

為了實(shí)現(xiàn) Runnable格二,一個(gè)類只需要執(zhí)行一個(gè)方法調(diào)用 run()

public void run()

我們可以重寫該方法网缝,重要的是理解的 run() 可以調(diào)用其它方法,使用其它類蟋定,并聲明變量,就像主線程一樣

創(chuàng)建一個(gè)實(shí)現(xiàn) Runnable 接口的類之后草添,我們就可以在類中實(shí)例化一個(gè)線程對(duì)象

Thread 定義了幾個(gè)構(gòu)造方法驶兜,下面這個(gè)則是經(jīng)常使用的

Thread(Runnable threadOb, String threadName);

  1. threadOb 是一個(gè)實(shí)現(xiàn) Runnable 接口的類的實(shí)例
  2. threadName 用于指定新線程的名字

新線程創(chuàng)建之后,我們可以調(diào)用它的 start() 方法它才會(huì)運(yùn)行

void start();

范例

下面的范例用于創(chuàng)建線程并開(kāi)始讓它執(zhí)行的實(shí)例

class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;

   RunnableDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }

   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // 讓線程睡眠一會(huì)
            Thread.sleep(50);
         }
      }catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }

   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}

public class TestThread {

   public static void main(String args[]) {
      RunnableDemo R1 = new RunnableDemo( "Thread-1");
      R1.start();

      RunnableDemo R2 = new RunnableDemo( "Thread-2");
      R2.start();
   }   
}

編譯運(yùn)行以上 Java 代碼远寸,輸出結(jié)果如下

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

通過(guò)繼承 Thread 來(lái)創(chuàng)建線程

創(chuàng)建一個(gè)線程的第二種方法是創(chuàng)建一個(gè)新的類抄淑,該類繼承 Thread 類,然后創(chuàng)建一個(gè)該類的實(shí)例

繼承類必須重寫 run() 方法驰后,該方法是新線程的入口點(diǎn)肆资,同時(shí)也必須調(diào)用 start() 方法才能執(zhí)行

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;

   ThreadDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }

   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // 讓線程睡醒一會(huì)
            Thread.sleep(50);
         }
      }catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }

   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}

public class TestThread {

   public static void main(String args[]) {
      ThreadDemo T1 = new ThreadDemo( "Thread-1");
      T1.start();

      ThreadDemo T2 = new ThreadDemo( "Thread-2");
      T2.start();
   }   
}

編譯運(yùn)行以上 Java 代碼,輸出結(jié)果如下

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

Thread 類的方法

下表列出了 Thread 類的一些重要方法

方法 描述
public void start() 使該線程開(kāi)始執(zhí)行灶芝;Java虛擬機(jī)調(diào)用該線程的 run 方法
public void run() 如果該線程是使用獨(dú)立的 Runnable 運(yùn)行對(duì)象構(gòu)造的郑原,則調(diào)用該 Runnable 對(duì)象的 run 方法唉韭;否則,該方法不執(zhí)行任何操作并返回
public final void setName(String name) 改變線程名稱犯犁,使之與參數(shù) name 相同
public final void setPriority(int priority) 更改線程的優(yōu)先級(jí)
public final void setDaemon(boolean on) 將該線程標(biāo)記為守護(hù)線程或用戶線程
public final void join(long millisec) 等待該線程終止的時(shí)間最長(zhǎng)為 millis 毫秒
public void interrupt() 中斷線程
public final boolean isAlive() 測(cè)試線程是否處于活動(dòng)狀態(tài)

下表列出了 Thread 類的靜態(tài)方法

方法描述
public static void yield()
暫停當(dāng)前正在執(zhí)行的線程對(duì)象属愤,并執(zhí)行其他線程
public static void sleep(long millisec)
在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),此操作受到系統(tǒng)計(jì)時(shí)器和調(diào)度程序精度和準(zhǔn)確性的影響
public static boolean holdsLock(Object x)
當(dāng)且僅當(dāng)當(dāng)前線程在指定的對(duì)象上保持監(jiān)視器鎖時(shí)酸役,才返回 true
public static Thread currentThread()
返回對(duì)當(dāng)前正在執(zhí)行的線程對(duì)象的引用
public static void dumpStack()
將當(dāng)前線程的堆棧跟蹤打印至標(biāo)準(zhǔn)錯(cuò)誤流

下面的范例演示了 Thread 類的一些方法的使用

public class DisplayMessage implements Runnable {
   private String message;

   public DisplayMessage(String message) {
      this.message = message;
   }

   public void run() {
      while(true) {
         System.out.println(message);
      }
   }
}

GuessANumber.java

public class GuessANumber extends Thread {
   private int number;
   public GuessANumber(int number) {
      this.number = number;
   }

   public void run() {
      int counter = 0;
      int guess = 0;
      do {
         guess = (int) (Math.random() * 100 + 1);
         System.out.println(this.getName() + " guesses " + guess);
         counter++;
      } while(guess != number);
      System.out.println("** Correct!" + this.getName() + "in" + counter + "guesses.**");
   }
}

ThreadClassDemo.java

public class ThreadClassDemo {

   public static void main(String [] args) {
      Runnable hello = new DisplayMessage("Hello");
      Thread thread1 = new Thread(hello);
      thread1.setDaemon(true);
      thread1.setName("hello");
      System.out.println("Starting hello thread...");
      thread1.start();

      Runnable bye = new DisplayMessage("Goodbye");
      Thread thread2 = new Thread(bye);
      thread2.setPriority(Thread.MIN_PRIORITY);
      thread2.setDaemon(true);
      System.out.println("Starting goodbye thread...");
      thread2.start();

      System.out.println("Starting thread3...");
      Thread thread3 = new GuessANumber(27);
      thread3.start();
      try {
         thread3.join();
      }catch(InterruptedException e) {
         System.out.println("Thread interrupted.");
      }
      System.out.println("Starting thread4...");
      Thread thread4 = new GuessANumber(75);

      thread4.start();
      System.out.println("main() is ending...");
   }
}

編譯運(yùn)行以上 Java 代碼住诸,輸出結(jié)果如下

Starting hello thread...
Starting goodbye thread...
Hello
Hello
Hello
Hello
Hello
Hello
Goodbye
Goodbye
Goodbye
Goodbye
Goodbye
.......

通過(guò) Callable 和 Future 創(chuàng)建線程

  1. 創(chuàng)建 Callable 接口的實(shí)現(xiàn)類,并實(shí)現(xiàn) call() 方法涣澡,該 call() 方法將作為線程執(zhí)行體贱呐,并且有返回值

  2. 創(chuàng)建 Callable 實(shí)現(xiàn)類的實(shí)例,使用 FutureTask 類來(lái)包裝 Callable 對(duì)象入桂,該 FutureTask 對(duì)象封裝了該 Callable 對(duì)象的 call() 方法的返回值

  3. 使用 FutureTask 對(duì)象作為 Thread 對(duì)象的 target 創(chuàng)建并啟動(dòng)新線程

  4. 調(diào)用 FutureTask 對(duì)象的 get() 方法來(lái)獲得子線程執(zhí)行結(jié)束后的返回值

范例

下面的范例使用 Callable 和 Future 創(chuàng)建線程

public class CallableThreadTest implements Callable<Integer> {
    public static void main(String[] args)  
    {  
        CallableThreadTest ctt = new CallableThreadTest();  
        FutureTask<Integer> ft = new FutureTask<>(ctt);  
        for(int i = 0;i < 100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" 的循環(huán)變量i的值"+i);  
            if(i==20)  
            {  
                new Thread(ft,"有返回值的線程").start();  
            }  
        }  
        try  
        {  
            System.out.println("子線程的返回值:"+ft.get());  
        } catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        } catch (ExecutionException e)  
        {  
            e.printStackTrace();  
        }  

    }
    @Override  
    public Integer call() throws Exception  
    {  
        int i = 0;  
        for(;i<100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" "+i);  
        }  
        return i;  
    }  
}

創(chuàng)建線程的三種方式的對(duì)比

  1. 采用實(shí)現(xiàn) Runnable奄薇、Callable 接口的方式創(chuàng)見(jiàn)多線程時(shí),線程類只是實(shí)現(xiàn)了 Runnable 接口或 Callable 接口事格,還可以繼承其他類

  2. 使用繼承 Thread 類的方式創(chuàng)建多線程時(shí)惕艳,編寫簡(jiǎn)單,如果需要訪問(wèn)當(dāng)前線程驹愚,則無(wú)需使用 Thread.currentThread() 方法远搪,直接使用 this 即可獲得當(dāng)前線程

多線程的使用

有效利用多線程的關(guān)鍵是理解程序是并發(fā)執(zhí)行而不是串行執(zhí)行的

例如:程序中有兩個(gè)子系統(tǒng)需要并發(fā)執(zhí)行,這時(shí)候就需要利用多線程編程

通過(guò)對(duì)多線程的使用逢捺,可以編寫出非常高效的程序

不過(guò)請(qǐng)注意谁鳍,如果創(chuàng)建太多的線程,程序執(zhí)行的效率實(shí)際上是降低了劫瞳,而不是提升了

請(qǐng)記住倘潜,上下文的切換開(kāi)銷也很重要,如果你創(chuàng)建了太多的線程志于,CPU 花費(fèi)在上下文的切換的時(shí)間將多于執(zhí)行程序的時(shí)間

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涮因,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子伺绽,更是在濱河造成了極大的恐慌养泡,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奈应,死亡現(xiàn)場(chǎng)離奇詭異澜掩,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)杖挣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門肩榕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人惩妇,你說(shuō)我怎么就攤上這事株汉】鹑椋” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵郎逃,是天一觀的道長(zhǎng)哥童。 經(jīng)常有香客問(wèn)我,道長(zhǎng)褒翰,這世上最難降的妖魔是什么贮懈? 我笑而不...
    開(kāi)封第一講書人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮优训,結(jié)果婚禮上朵你,老公的妹妹穿的比我還像新娘。我一直安慰自己揣非,他們只是感情好抡医,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著早敬,像睡著了一般忌傻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搞监,一...
    開(kāi)封第一講書人閱讀 51,245評(píng)論 1 299
  • 那天水孩,我揣著相機(jī)與錄音,去河邊找鬼琐驴。 笑死俘种,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绝淡。 我是一名探鬼主播宙刘,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼牢酵!你這毒婦竟也來(lái)了悬包?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤馍乙,失蹤者是張志新(化名)和其女友劉穎布近,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體潘拨,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年饶号,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铁追。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茫船,死狀恐怖琅束,靈堂內(nèi)的尸體忽然破棺而出扭屁,到底是詐尸還是另有隱情,我是刑警寧澤涩禀,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布料滥,位于F島的核電站,受9級(jí)特大地震影響艾船,放射性物質(zhì)發(fā)生泄漏葵腹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一屿岂、第九天 我趴在偏房一處隱蔽的房頂上張望践宴。 院中可真熱鬧,春花似錦爷怀、人聲如沸阻肩。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)烤惊。三九已至,卻和暖如春吁朦,著一層夾襖步出監(jiān)牢的瞬間柒室,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工喇完, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伦泥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓锦溪,卻偏偏與公主長(zhǎng)得像不脯,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刻诊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354

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

  • Java給多線程編程提供了內(nèi)置的支持防楷。一個(gè)多線程程序包含兩個(gè)或多個(gè)能并發(fā)運(yùn)行的部分。程序的每一部分都稱作一個(gè)線程则涯,...
    編程小世界閱讀 363評(píng)論 0 1
  • 概念 進(jìn)程:是具有一定獨(dú)立功能的程序复局、它是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位,重點(diǎn)在系統(tǒng)調(diào)度和單獨(dú)的單位粟判,也就是...
    我肚子里沒(méi)有墨水閱讀 210評(píng)論 0 0
  • 前言 在開(kāi)發(fā)中我們經(jīng)常使用線程來(lái)優(yōu)化程序亿昏,提高系統(tǒng)執(zhí)行效率,今天我們就來(lái)簡(jiǎn)單概述一下Java開(kāi)發(fā)過(guò)程中需要了解的多...
    Java資訊庫(kù)閱讀 585評(píng)論 1 3
  • 進(jìn)程與線程 ??在Java語(yǔ)言里面最大的特點(diǎn)是支持多線程的開(kāi)發(fā)(也是為數(shù)不多支持多線程的編程語(yǔ)言)档礁,所以在整個(gè)Ja...
    江湖非良人閱讀 844評(píng)論 1 7
  • 文章來(lái)源:http://www.54tianzhisheng.cn/2017/06/04/Java-Thread/...
    beneke閱讀 1,483評(píng)論 0 1