Java的多線程編程

Java 給多線程編程提供了內(nèi)置的支持贤笆。 一條線程指的是進(jìn)程中一個(gè)單一順序的控制流蝇棉,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)芥永。

多線程是多任務(wù)的一種特別的形式篡殷,但多線程使用了更小的資源開銷。多線程能滿足程序員編寫高效率的程序來達(dá)到充分利用 CPU 的目的埋涧。

這里定義和線程相關(guān)的另一個(gè)術(shù)語(yǔ) - 進(jìn)程:一個(gè)進(jìn)程包括由操作系統(tǒng)分配的內(nèi)存空間板辽,包含一個(gè)或多個(gè)線程。一個(gè)線程不能獨(dú)立的存在棘催,它必須是進(jìn)程的一部分劲弦。一個(gè)進(jìn)程一直運(yùn)行,直到所有的非守護(hù)線程都結(jié)束運(yùn)行后才能結(jié)束醇坝。

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

線程是一個(gè)動(dòng)態(tài)執(zhí)行的過程邑跪,它也有一個(gè)從產(chǎn)生到死亡的過程。下圖顯示了一個(gè)線程完整的生命周期:

java-thread.jpg

新建狀態(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被其他線程占用)。
  • 其他阻塞:通過調(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í)鬼雀,線程的優(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)建線程的方法:

  • 通過實(shí)現(xiàn) Runnable 接口俊性;
  • 通過繼承 Thread 類本身略步;
  • 通過 Callable 和 Future 創(chuàng)建線程。

通過實(shí)現(xiàn) Runnable 接口來創(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)常使用的:

// threadOb 是一個(gè)實(shí)現(xiàn) Runnable 接口的類的實(shí)例,并且 threadName 指定新線程的名字
Thread(Runnable threadOb,String threadName);

新線程創(chuàng)建之后雷恃,調(diào)用它的 start() 方法它才會(huì)運(yùn)行疆股。聲明如下:

void start();

下面是一個(gè)創(chuàng)建線程并開始讓它執(zhí)行的實(shí)例:

public class RunnableDemo implements Runnable{
    // 線程對(duì)象
    private Thread t;
    // 線程名稱
    private String threadName;

    // 構(gòu)造方法
    RunnableDemo( String name) {
        threadName = name;
        System.out.println("Creating " +  threadName );
    }

    // 實(shí)現(xiàn) run() 抽象方法
    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.");
    }

    // 運(yùn)行線程
    public void start () {
        System.out.println("Starting " +  threadName );
        if (t == null) {
            t = new Thread (this, threadName);
            // 線程開始執(zhí)行
            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)行結(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-2 exiting.
// Thread Thread-1 exiting.

// Process finished with exit code 0

通過繼承Thread來創(chuàng)建線程:創(chuàng)建一個(gè)線程的第二種方法是創(chuàng)建一個(gè)新的類,該類繼承 Thread 類褂萧,然后創(chuàng)建一個(gè)該類的實(shí)例押桃。繼承類必須重寫 run() 方法,該方法是新線程的入口點(diǎn)导犹。它也必須調(diào)用 start() 方法才能執(zhí)行。通過繼承Thread來創(chuàng)建線程盡管被列為一種多線程實(shí)現(xiàn)方式羡忘,但是本質(zhì)上也是實(shí)現(xiàn)了 Runnable 接口的一個(gè)實(shí)例:

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)行結(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-2, 2
// Thread: Thread-1, 2
// Thread: Thread-2, 1
// Thread: Thread-1, 1
// Thread Thread-1 exiting.
// Thread Thread-2 exiting.

// Process finished with exit code 0

下表列出了Thread類的一些重要方法(Thread 對(duì)象調(diào)用):

序號(hào) 方法及說明
1 public void start()谎痢,使該線程開始執(zhí)行;Java 虛擬機(jī)調(diào)用該線程的 run 方法
2 public void run()卷雕,如果該線程是使用獨(dú)立的 Runnable 運(yùn)行對(duì)象構(gòu)造的节猿,則調(diào)用該 Runnable 對(duì)象的 run 方法;否則漫雕,該方法不執(zhí)行任何操作并返回
3 public final void setName(String name)滨嘱,改變線程名稱,使之與參數(shù) name 相同
4 public final void setPriority(int priority)浸间,更改線程的優(yōu)先級(jí)
5 public final void setDaemon(boolean on)太雨,將該線程標(biāo)記為守護(hù)線程或用戶線程
6 public final void join(long millisec),會(huì)讓運(yùn)行到(不是調(diào)用方)該方法的線程處于阻塞狀態(tài)魁蒜,阻塞的最長(zhǎng)時(shí)間為 millis 毫秒(0或無參會(huì)無限等待)囊扳,知道調(diào)用該方法的線程運(yùn)行完畢或是到達(dá)等待最長(zhǎng)時(shí)間后解除運(yùn)行該方法的線程的阻塞狀態(tài)
7 public void interrupt(),中斷一個(gè)正在運(yùn)行的線程兜看,若是該線程正處于某種阻塞時(shí)被調(diào)用中斷方法中斷锥咸,那么并不是將該線程直接中斷,而是中斷其阻塞狀態(tài)细移,這時(shí)通常會(huì)拋出異常搏予,通知程序該線程的阻塞狀態(tài)被打斷
8 public final boolean isAlive(),測(cè)試線程是否處于活動(dòng)狀態(tài)

下面表格的方法是 Thread 類的靜態(tài)方法:

序號(hào) 方法及說明
1 public static void yield()弧轧,暫停當(dāng)前正在執(zhí)行的線程對(duì)象雪侥,并執(zhí)行其他線程
2 public static void sleep(long millisec)球涛,在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),此操作受到系統(tǒng)計(jì)時(shí)器和調(diào)度程序精度和準(zhǔn)確性的影響校镐。
3 public static boolean holdsLock(Object x)亿扁,當(dāng)且僅當(dāng)當(dāng)前線程在指定的對(duì)象上保持監(jiān)視器鎖時(shí),才返回 true
4 public static Thread currentThread()鸟廓,返回對(duì)當(dāng)前正在執(zhí)行的線程對(duì)象的引用
5 public static void dumpStack()从祝,將當(dāng)前線程的堆棧跟蹤打印至標(biāo)準(zhǔn)錯(cuò)誤流

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

    1. 創(chuàng)建 Callable 接口的實(shí)現(xiàn)類,并實(shí)現(xiàn) call() 方法引谜,該 call() 方法將作為線程執(zhí)行體牍陌,并且有返回值。
    1. 創(chuàng)建 Callable 實(shí)現(xiàn)類的實(shí)例员咽,使用 FutureTask 類來包裝 Callable 對(duì)象毒涧,該 FutureTask 對(duì)象封裝了該 Callable 對(duì)象的 call() 方法的返回值。
    1. 使用 FutureTask 對(duì)象作為 Thread 對(duì)象的 target 創(chuàng)建并啟動(dòng)新線程贝室。
    1. 調(diào)用 FutureTask 對(duì)象的 get() 方法來獲得子線程執(zhí)行結(jié)束后的返回值契讲。
package Thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

// 創(chuàng)建 Callable 接口的實(shí)現(xiàn)類
public class CallableThreadTest implements Callable {
    public static void main(String[] args)
    {
        //  創(chuàng)建 Callable 實(shí)現(xiàn)類的實(shí)例,使用 FutureTask 類來包裝 Callable 對(duì)象
        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)
            {
                // 以 FutureTask 對(duì)象 和 新線程的名稱 創(chuàng)建并啟動(dòng)子線程
                new Thread(ft,"有返回值的線程").start();
            }
        }
        try
        {
            // 輸出子線程執(zhí)行結(jié)束后的返回值
            System.out.println("子線程的返回值:"+ft.get());
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        } catch (ExecutionException e)
        {
            e.printStackTrace();
        }

    }
    // 實(shí)現(xiàn) call() 方法滑频,該 call() 方法將作為線程執(zhí)行體捡偏,并且有返回值
    @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)建多線程時(shí)峡迷,線程類只是實(shí)現(xiàn)了 Runnable 接口或 Callable 接口银伟,還可以繼承其他類。
    1. 使用繼承 Thread 類的方式創(chuàng)建多線程時(shí)绘搞,編寫簡(jiǎn)單彤避,如果需要訪問當(dāng)前線程,則無需使用 Thread.currentThread() 方法夯辖,直接使用 this 即可獲得當(dāng)前線程琉预。

線程的幾個(gè)主要概念

在多線程編程時(shí),需要了解以下幾個(gè)概念:

  • 線程同步
  • 線程死鎖
  • 線程間通信
  • 線程控制:掛起楼雹、停止和恢復(fù)

線程同步:即當(dāng)有一個(gè)線程在對(duì)內(nèi)存進(jìn)行操作時(shí)模孩,其他線程都不可以對(duì)這個(gè)內(nèi)存地址進(jìn)行操作,直到該線程完成操作贮缅, 其他線程才能對(duì)該內(nèi)存地址進(jìn)行操作榨咐。

同步方法:在一個(gè)方法上使用 synchronized 修飾后,那么該方法稱為“同步方法”谴供,即多個(gè)線程不能同時(shí)在方法內(nèi)部執(zhí)行块茁,從而解決并發(fā)安全問題。靜態(tài)方法使用 synchronized 修飾后,那么該方法一定具有同步效果数焊。聲明格式如下:

public synchronized 返回值類型 方法名(){}

同步塊:在需要同步運(yùn)行的代碼片段上使用 synchronized 修飾后永淌,那么該代碼塊稱為“同步塊”,有效的縮小同步范圍可以在保證并發(fā)安全的前提下盡可能的提高并發(fā)效率佩耳。聲明格式如下:

synchronized (同步監(jiān)視器) {
   需要同步運(yùn)行的代碼片段
}

同步監(jiān)視器:同步監(jiān)視器可以是 Java 中任意的一個(gè)對(duì)象遂蛀。在“同步方法”中,同步監(jiān)視器對(duì)象就是當(dāng)前方法所屬對(duì)象干厚,即方法內(nèi)部看到的 this李滴。在“同步塊”中,只要保證多個(gè)線程看到的該對(duì)象是‘同一個(gè)’蛮瞄,即可保證同步塊中的代碼是并發(fā)安全的所坯。

使用“同步塊”的實(shí)例:

package Thread;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

// 創(chuàng)建 Callable 接口的實(shí)現(xiàn)類
public class CallableThreadTest implements Callable {
    public static void main(String[] args)
    {
        //  創(chuàng)建 Callable 實(shí)現(xiàn)類的實(shí)例,使用 FutureTask 類來包裝 Callable 對(duì)象
        CallableThreadTest ctt = new CallableThreadTest();
        FutureTask<Integer> ft = new FutureTask<>(ctt);
        FutureTask<Integer> ft2 = new FutureTask<>(ctt);
       
        // 以 FutureTask 對(duì)象 和 新線程的名稱 創(chuàng)建并啟動(dòng)子線程
        Thread t1 = new Thread(ft,"有返回值的線程");
        t1.start();
        Thread t2 = new Thread(ft2,"有返回值的線程2");
        t2.start();
    }
    // 實(shí)現(xiàn) call() 方法挂捅,該 call() 方法將作為線程執(zhí)行體芹助,并且有返回值
    @Override
    public Integer call() throws Exception
    {
        synchronized (this){
            int i = 0;
            for(;i<100;i++)
            {
            // 子線程輸出
            System.out.println(Thread.currentThread().getName()+" "+i);

            }
            return I;
        }
    }
}

// 程序運(yùn)行結(jié)果如下:
// 使用同步塊的情況下,一個(gè)線程會(huì)完成循環(huán)后闲先,另一個(gè)線程再開始循環(huán)
// 不實(shí)用同步塊的情況下状土,兩個(gè)線程中的循環(huán)交替進(jìn)行

線程互斥鎖:當(dāng)使用 synchronized 鎖住多段不同的代碼片段,但是這些同步塊使用的同步監(jiān)視器對(duì)象是同一個(gè)時(shí)饵蒂,那么這些代碼片段之間就是互斥的声诸,多個(gè)線程不能同時(shí)執(zhí)行他們。

線程死鎖:當(dāng)多個(gè)線程都持有自己的鎖退盯,但都等對(duì)方先釋放鎖時(shí),就會(huì)出現(xiàn)‘僵持’的情況泻肯,使得所有線程進(jìn)入阻塞狀態(tài)渊迁,這個(gè)現(xiàn)象稱為死鎖現(xiàn)象。

線程間通信:當(dāng)多個(gè)線程共同操作共享的資源時(shí)灶挟,線程間通過某種方式互相告知自己的狀態(tài)琉朽,以避免無效的資源爭(zhēng)奪。線程間通信的方式可以有很多種:等待-通知稚铣、共享內(nèi)存箱叁、管道流。每種方式用不同的方法來實(shí)現(xiàn)惕医。

線程控制:Java提供對(duì)多線程程序的完全控制耕漱。可以開發(fā)一個(gè)多線程程序抬伺,根據(jù)要求完全暫停螟够,恢復(fù)或停止。可以在線程對(duì)象上使用各種靜態(tài)方法來控制它們的行為妓笙。

  • 掛起: 線程掛起就是指暫停線程的執(zhí)行(阻塞狀態(tài))若河,掛起時(shí)線程不會(huì)釋放對(duì)象鎖。
  • 恢復(fù): 恢復(fù)就是讓暫停的線程得以繼續(xù)執(zhí)行.(返回就緒狀態(tài))寞宫。
  • 停止:停止一個(gè)線程時(shí)會(huì)強(qiáng)制結(jié)束線程的執(zhí)行萧福,不管run方法是否執(zhí)行完了,并且還會(huì)釋放這個(gè)線程所持有的所有的鎖對(duì)象辈赋。

死鎖及解決方法

死鎖是這樣一種情形:多個(gè)線程同時(shí)被阻塞鲫忍,它們中的一個(gè)或者全部都在等待某個(gè)資源被釋放。由于線程被無限期地阻塞炭庙,因此程序不可能正常終止饲窿。

java 死鎖產(chǎn)生的四個(gè)必要條件

  • 1、互斥使用焕蹄,即當(dāng)資源被一個(gè)線程使用(占有)時(shí)逾雄,別的線程不能使用。
  • 2腻脏、不可搶占鸦泳,資源請(qǐng)求者不能強(qiáng)制從資源占有者手中奪取資源,資源只能由資源占有者主動(dòng)釋放永品。
  • 3做鹰、請(qǐng)求和保持,即當(dāng)資源請(qǐng)求者在請(qǐng)求其他的資源的同時(shí)保持對(duì)原有資源的占有鼎姐。
  • 4钾麸、循環(huán)等待,即存在一個(gè)等待隊(duì)列:P1占有P2的資源炕桨,P2占有P3的資源饭尝,P3占有P1的資源。這樣就形成了一個(gè)等待環(huán)路献宫。

當(dāng)上述四個(gè)條件都成立的時(shí)候钥平,便形成死鎖。當(dāng)然姊途,死鎖的情況下如果打破上述任何一個(gè)條件涉瘾,便可讓死鎖消失。下面用java代碼來模擬一下死鎖的產(chǎn)生:

public class LockTest {
    public static String obj1 = "obj1";
    public static String obj2 = "obj2";
    public static void main(String[] args) {
        LockA la = new LockA();
        new Thread(la).start();
        LockB lb = new LockB();
        new Thread(lb).start();
    }
}

class LockA implements Runnable{
    public void run() {
        try {
            System.out.println(new Date().toString() + " LockA 開始執(zhí)行");
            while(true){
                synchronized (LockTest.obj1) {
                    System.out.println(new Date().toString() + " LockA 鎖住 obj1");
                    // 此處等待是給B能鎖住機(jī)會(huì)
                    Thread.sleep(3000);
                    synchronized (LockTest.obj2) {
                        System.out.println(new Date().toString() + " LockA 鎖住 obj2");
                        // 為測(cè)試捷兰,占用了就不放
                        Thread.sleep(60 * 1000);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class LockB implements Runnable{
    public void run() {
        try {
            System.out.println(new Date().toString() + " LockB 開始執(zhí)行");
            while(true){
                synchronized (LockTest.obj2) {
                    System.out.println(new Date().toString() + " LockB 鎖住 obj2");
                    // 此處等待是給A能鎖住機(jī)會(huì)
                    Thread.sleep(3000);
                    synchronized (LockTest.obj1) {
                        System.out.println(new Date().toString() + " LockB 鎖住 obj1");
                        // 為測(cè)試立叛,占用了就不放
                        Thread.sleep(60 * 1000);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 以上程序執(zhí)行結(jié)果為:
// Fri May 12 12:59:39 CST 2023 LockA 開始執(zhí)行
// Fri May 12 12:59:39 CST 2023 LockB 開始執(zhí)行
// Fri May 12 12:59:39 CST 2023 LockA 鎖住 obj1
// Fri May 12 12:59:39 CST 2023 LockB 鎖住 obj2

由于不恰當(dāng)?shù)氖褂昧随i,且出現(xiàn)同時(shí)鎖住多個(gè)對(duì)象時(shí)寂殉,上例中便發(fā)生了死鎖囚巴,LockA 的 run() 方法執(zhí)行后,率先鎖住 obj1 對(duì)象,之后 LockA 中的線程進(jìn)入休眠彤叉,期間 LockB 的 run() 方法中鎖住 obj2 對(duì)象庶柿。當(dāng) LockA 中的線程睡醒后,準(zhǔn)備去鎖住 obj2 對(duì)象秽浇,結(jié)果發(fā)現(xiàn) obj2 對(duì)象已經(jīng)被 LockB 鎖住浮庐,只能站著等 LockB 撒手。而 LockB 中的線程睡醒后柬焕,準(zhǔn)備去鎖住 obj1 對(duì)象审残,結(jié)果發(fā)現(xiàn) obj1 對(duì)象已經(jīng)被 LockA 鎖住,結(jié)果也只能站著等 LockA 撒手斑举。最終 LockA 與 LockB 都站下了搅轿,程序也就站下了。

為了解決這個(gè)問題富玷,我們可以不使用顯示的去鎖璧坟,而使用信號(hào)量去控制。信號(hào)量可以控制資源能被多少線程訪問赎懦,這里我們指定只能被一個(gè)線程訪問雀鹃,就做到了類似鎖住。而信號(hào)量可以指定去獲取的超時(shí)時(shí)間励两,我們可以根據(jù)這個(gè)超時(shí)時(shí)間黎茎,去做一個(gè)額外處理。對(duì)于無法成功獲取的情況当悔,可以采取重復(fù)嘗試傅瞻,或指定嘗試的次數(shù),也可以馬上退出:

public class UnLockTest {
    // 同步觀察對(duì)象
    public static String obj1 = "obj1";
    public static String obj2 = "obj2";
    // 信號(hào)量盲憎,初始許可操作數(shù)為1
    public static final Semaphore a1 = new Semaphore(1);
    public static final Semaphore a2 = new Semaphore(1);
    
    public static void main(String[] args) {
        NewLockA la = new NewLockA();
        new Thread(la).start();
        NewLockB lb = new NewLockB();
        new Thread(lb).start();
    }
}

class NewLockA implements Runnable {
    public void run() {
        try {
            System.out.println(new Date().toString() + " LockA 開始執(zhí)行");
            // 無限次的重復(fù)嘗試獲取鎖俭正,直到成功,對(duì)焙畔,就是這么執(zhí)著
            while (true) {
                // 判斷信號(hào)量 a1 是否許可使用,等待信號(hào)量 a1 許可的時(shí)間最長(zhǎng) 1 秒
                if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
                    System.out.println(new Date().toString() + " LockA 鎖住 obj1");
                    // 判斷信號(hào)量 a2 是否許可使用串远,等待信號(hào)量 a2 許可的時(shí)間最長(zhǎng) 1 秒
                    if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
                        System.out.println(new Date().toString() + " LockA 鎖住 obj2");
                        // LockA 同時(shí)鎖住 obj1 與 obj2宏多,這里可以進(jìn)行操作,睡一下
                        System.out.println("LockA 可以安心操作了");
                        Thread.sleep(10 * 1000);
                        // 釋放信號(hào)量
                        UnLockTest.a1.release();
                        UnLockTest.a2.release();
                        // 跳出循環(huán)
                        break;
                    }else{
                        // 信號(hào)量 a2 不可用澡罚、或等待信號(hào)量許可超時(shí)
                        System.out.println(new Date().toString() + " LockA 鎖 obj2 失敗");
                    }
                }else{
                    // 信號(hào)量 a1 不可用伸但、或等待信號(hào)量許可超時(shí)
                    System.out.println(new Date().toString() + " LockA 鎖 obj1 失敗");
                }
                // 釋放信號(hào)量
                UnLockTest.a1.release();
                UnLockTest.a2.release();
                // 上鎖失敗了,暫停當(dāng)前正在執(zhí)行的線程對(duì)象留搔,并執(zhí)行其他線程
                Thread.yield();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class NewLockB implements Runnable {
    public void run() {
        try {
            System.out.println(new Date().toString() + " LockB 開始執(zhí)行");
            // 無限次的重復(fù)嘗試獲取鎖更胖,直到成功,對(duì),就是這么執(zhí)著
            while (true) {
                if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
                    System.out.println(new Date().toString() + " LockB 鎖住 obj2");
                    if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
                        System.out.println(new Date().toString() + " LockB 鎖住 obj1");
                        // LockB 同時(shí)鎖住 obj1 與 obj2却妨,這里可以進(jìn)行操作饵逐,睡一下
                        System.out.println("LockB 可以安心操作了");
                        Thread.sleep(10 * 1000);
                        // 釋放信號(hào)量
                        UnLockTest.a1.release();
                        UnLockTest.a2.release();
                        // 跳出循環(huán)
                        break;
                    }else{
                        System.out.println(new Date().toString() + " LockB 鎖 obj1 失敗");
                    }
                }else{
                    System.out.println(new Date().toString() + " LockB 鎖 obj2 失敗");
                }
                // 釋放信號(hào)量
                UnLockTest.a1.release();
                UnLockTest.a2.release();
                // 上鎖失敗了,暫停當(dāng)前正在執(zhí)行的線程對(duì)象彪标,并執(zhí)行其他線程
                Thread.yield();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 以上程序執(zhí)行結(jié)果為:
// Fri May 12 21:39:40 CST 2023 LockB 開始執(zhí)行
// Fri May 12 21:39:40 CST 2023 LockA 開始執(zhí)行
// Fri May 12 21:39:40 CST 2023 LockB 鎖住 obj2
// Fri May 12 21:39:40 CST 2023 LockA 鎖住 obj1
// Fri May 12 21:39:41 CST 2023 LockB 鎖 obj1 失敗
// Fri May 12 21:39:41 CST 2023 LockB 鎖住 obj2
// Fri May 12 21:39:41 CST 2023 LockA 鎖 obj2 失敗
// Fri May 12 21:39:41 CST 2023 LockB 鎖住 obj1
// LockB 可以安心操作了
// Fri May 12 21:39:41 CST 2023 LockA 鎖住 obj1
// Fri May 12 21:39:41 CST 2023 LockA 鎖住 obj2
// LockA 可以安心操作了

生產(chǎn)者/消費(fèi)者問題

生產(chǎn)者和消費(fèi)者問題是線程模型中的經(jīng)典問題:生產(chǎn)者和消費(fèi)者在同一時(shí)間段內(nèi)共用同一個(gè)存儲(chǔ)空間倍权,如下圖所示,生產(chǎn)者向空間里存放數(shù)據(jù)捞烟,而消費(fèi)者取用數(shù)據(jù)薄声,如果不加以協(xié)調(diào)可能會(huì)出現(xiàn)以下情況:

  • 存儲(chǔ)空間已滿,而生產(chǎn)者占用著它题画,消費(fèi)者等著生產(chǎn)者讓出空間從而取出產(chǎn)品默辨,生產(chǎn)者等著消費(fèi)者消費(fèi)產(chǎn)品,從而向空間中添加產(chǎn)品苍息∷跣遥互相等待,從而發(fā)生死鎖档叔。
2011091018554595.gif

以下實(shí)例演示了如何通過線程解決生產(chǎn)者/消費(fèi)者問題:

public class ProducerConsumerTest {
    public static void main(String[] args) {
        CubbyHole c = new CubbyHole();
        Producer p1 = new Producer(c, 1);
        p1.setPriority(Thread.MAX_PRIORITY);
        p1.start();
        Consumer c1 = new Consumer(c, 1);
        c1.setPriority(Thread.MIN_PRIORITY);
        c1.start();
    }
}

/**
 * 倉(cāng)庫(kù)類
 */
class CubbyHole {
    // 倉(cāng)庫(kù)中的產(chǎn)品桌粉,這里的產(chǎn)品是一個(gè)整數(shù)
    private int contents;
    // 是否可以在倉(cāng)庫(kù)中取出產(chǎn)品
    private boolean available = false;

    /**
     * 同步方法,在倉(cāng)庫(kù)獲取產(chǎn)品
     * @return 產(chǎn)品
     */
    public synchronized int get() {
        // 如果不可以在倉(cāng)庫(kù)中取出產(chǎn)品衙四,就應(yīng)該等待倉(cāng)庫(kù)放入商品铃肯,所以該對(duì)象上的線程就進(jìn)入無限期的等待
        while (available == false) {
            try {
                wait();
            }
            catch (InterruptedException e) {
            }
        }
        // 如果可以在倉(cāng)庫(kù)中取出產(chǎn)品,取出商品传蹈,喚醒在該對(duì)象上等待的所有線程押逼,再次標(biāo)記為不可以在倉(cāng)庫(kù)中取出商品
        available = false;
        notifyAll();
        return contents;
    }

    /**
     * 同步方法,向倉(cāng)庫(kù)放入商品
     * @param value 商品
     */
    public synchronized void put(int value) {
        // 如果可以在倉(cāng)庫(kù)中取出產(chǎn)品惦界,那么就不再放入商品挑格,所以該對(duì)象上的線程就進(jìn)入無限期的等待
        while (available == true) {
            try {
                wait();
            }
            catch (InterruptedException e) {
            }
        }
        // 如果不可以在倉(cāng)庫(kù)中取出產(chǎn)品了,說明倉(cāng)庫(kù)沒貨了沾歪,可以放入商品
        contents = value;
        // 放入商品后漂彤,標(biāo)記為可以取出產(chǎn)品了
        available = true;
        // 喚醒在該對(duì)象上等待的所有線程
        notifyAll();
    }
}

/**
 * 生產(chǎn)者類
 */
class Producer extends Thread {
    // 倉(cāng)庫(kù)對(duì)象
    private CubbyHole cubbyhole;
    // 生產(chǎn)者編號(hào)
    private int number;

    /**
     * 生產(chǎn)者構(gòu)造方法
     * @param c 倉(cāng)庫(kù)對(duì)象
     * @param number 生產(chǎn)者編號(hào)
     */
    public Producer(CubbyHole c, int number) {
        cubbyhole = c;
        this.number = number;
    }

    public void run() {
        for (int i = 0; i < 5; i++) {
            // 向倉(cāng)庫(kù)放入商品
            cubbyhole.put(i);
            System.out.println("生產(chǎn)者 #" + this.number + " 向倉(cāng)庫(kù)放入: " + i);
            try {
                // 睡一下,讓出Cpu時(shí)間片
                sleep((int)(Math.random() * 100));
            } catch (InterruptedException e) { }
        }
    }
}

/**
 * 消費(fèi)者類
 */
class Consumer extends Thread {
    // 倉(cāng)庫(kù)對(duì)象
    private CubbyHole cubbyhole;
    // 消費(fèi)者編號(hào)
    private int number;

    /**
     * 消費(fèi)者構(gòu)造方法
     * @param c 倉(cāng)庫(kù)對(duì)象
     * @param number 消費(fèi)者編號(hào)
     */
    public Consumer(CubbyHole c, int number) {
        cubbyhole = c;
        this.number = number;
    }
    public void run() {
        int value = 0;
        for (int i = 0; i < 5; i++) {
            // 在倉(cāng)庫(kù)取出商品
            value = cubbyhole.get();
            System.out.println("消費(fèi)者 #" + this.number+ " 在倉(cāng)庫(kù)取出: " + value);
        }
    }
}

// 以上程序執(zhí)行結(jié)果為:
// 生產(chǎn)者 #1 向倉(cāng)庫(kù)放入: 0
// 消費(fèi)者 #1 在倉(cāng)庫(kù)取出: 0
// 生產(chǎn)者 #1 向倉(cāng)庫(kù)放入: 1
// 消費(fèi)者 #1 在倉(cāng)庫(kù)取出: 1
// 生產(chǎn)者 #1 向倉(cāng)庫(kù)放入: 2
// 消費(fèi)者 #1 在倉(cāng)庫(kù)取出: 2
// 生產(chǎn)者 #1 向倉(cāng)庫(kù)放入: 3
// 消費(fèi)者 #1 在倉(cāng)庫(kù)取出: 3
// 生產(chǎn)者 #1 向倉(cāng)庫(kù)放入: 4
// 消費(fèi)者 #1 在倉(cāng)庫(kù)取出: 4

多線程的使用

有效利用多線程的關(guān)鍵是理解程序是并發(fā)執(zhí)行而不是串行執(zhí)行的灾搏。例如:程序中有兩個(gè)子系統(tǒng)需要并發(fā)執(zhí)行挫望,這時(shí)候就需要利用多線程編程。通過對(duì)多線程的使用狂窑,可以編寫出非常高效的程序媳板。不過請(qǐng)注意,如果創(chuàng)建太多的線程泉哈,程序執(zhí)行的效率實(shí)際上是降低了蛉幸,而不是提升了破讨。因?yàn)樯舷挛牡那袚Q開銷也很重要,如果創(chuàng)建了太多的線程奕纫,CPU 花費(fèi)在上下文的切換的時(shí)間將多于執(zhí)行程序的時(shí)間提陶!

線程池

線程池是一個(gè)容納多個(gè)線程的容器,可以實(shí)現(xiàn)線程的復(fù)用若锁,避免了反復(fù)創(chuàng)建線程的資源消耗搁骑。線程池的主要作用如下:

  • 控制線程數(shù)量:每個(gè)線程都會(huì)占用進(jìn)程的一部分內(nèi)存,線程的數(shù)量過多會(huì)導(dǎo)致資源消耗大又固,由于所有的線程都是并發(fā)運(yùn)行仲器,那么過多的線程也會(huì)導(dǎo)致CPU過度切換,導(dǎo)致并發(fā)效率變差仰冠。
  • 重用線程:頻繁的創(chuàng)建銷毀線程會(huì)給線程調(diào)度帶來負(fù)擔(dān)乏冀,所以也應(yīng)當(dāng)重用線程。

可以使用 Runnable 接口創(chuàng)建線程池:

public class ThreadPool {
    public static void main(String[] args) {
        // 創(chuàng)建任務(wù)
        TaskRunnable task = new TaskRunnable();
        // 創(chuàng)建固定大小的線程池
        ExecutorService threadPool =  Executors.newFixedThreadPool(2);
        // 將任務(wù)指派給線程池
        // execute只能提交Runnable類型的任務(wù)洋只,沒有返回值
        threadPool.execute(task);
        threadPool.execute(task);
        threadPool.execute(task);
        // 有序的停止線程池中的任務(wù)辆沦,在 awaitTermination() 方法中等待子線程任務(wù)完成后,停止線程池
        threadPool.shutdown();
        //等待直到所有任務(wù)完成
        try {
            System.out.println("等待子線程完成任務(wù)识虚。肢扯。。");
            // 阻塞担锤,等待子線程任務(wù)完成蔚晨,或達(dá)到超時(shí)時(shí)間,解除阻塞
            threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("任務(wù)都完成了肛循,退出程序铭腕!");
    }
}

/**
 * 任務(wù)類
 */
class TaskRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("子線程任務(wù)開始了。多糠。累舷。");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException x) {
            System.out.println(x);
        }
        System.out.println("子線程任務(wù)完成了!");
    }
}

// 以上程序執(zhí)行結(jié)果為:
// 等待子線程完成任務(wù)。夹孔。被盈。
// 子線程任務(wù)開始了。搭伤。害捕。
// 子線程任務(wù)開始了。闷畸。。
// 子線程任務(wù)完成了!
// 子線程任務(wù)開始了吞滞。佑菩。盾沫。
// 子線程任務(wù)完成了!
// 子線程任務(wù)完成了!
// 任務(wù)都完成了,退出程序殿漠!

使用Callable接口創(chuàng)建線程池:

public class ThreadPool {
    public static void main(String[] args) {
        // 創(chuàng)建任務(wù)
        TaskCallable task = new TaskCallable("future 任務(wù)");
        TaskCallable task2 = new TaskCallable("普通任務(wù)");
        // 創(chuàng)建固定大小的線程池
        ExecutorService threadPool =  Executors.newFixedThreadPool(2);
        // submit 可以提交 Callable 與 Runnable 類型的任務(wù)赴精,有返回值
        Future future = threadPool.submit(task);
        threadPool.submit(task2);
        threadPool.submit(task2);
        threadPool.submit(task2);
        try {
            System.out.println("阻塞,至少等待 future 任務(wù)完成");
            // 阻塞線程绞幌,等待結(jié)果
            future.get();
        }catch (Exception e) {
            System.out.println(e);
        }
        // 盡力終止線程池執(zhí)行任務(wù)(不保證一定終止)蕾哟,通過Thread.interrupt終止
        // 例如線程休眠的話,該方法不會(huì)在 awaitTermination() 方法中等待
        threadPool.shutdownNow();
        // 等待直到所有任務(wù)完成莲蜘,這里 shutdownNow() 方法不會(huì)在這里等待谭确, 所以無效的
        try {
            System.out.println("等待子線程完成任務(wù)。票渠。逐哈。");
            // 阻塞,等待子線程任務(wù)完成问顷,或達(dá)到超時(shí)時(shí)間昂秃,解除阻塞
            threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("任務(wù)都完成了,退出程序杜窄!");
    }
}

/**
 * 任務(wù)類
 */
class TaskCallable implements Callable {
    private String taskName;

    TaskCallable(String taskName) {
        this.taskName = taskName;
    }

    @Override
    public Object call() throws Exception {
        System.out.println(this.taskName + "開始了肠骆。。塞耕。");
        try {
            Thread.sleep(2000);
            System.out.println(this.taskName + "完成了!");
        } catch (InterruptedException x) {
            System.out.println(this.taskName + "被打斷了!!");
        }
        return null;
    }
}

// 以上程序執(zhí)行結(jié)果為:
// future 任務(wù)開始了蚀腿。。荷科。
// 阻塞唯咬,至少等待 future 任務(wù)完成
// 普通任務(wù)開始了。畏浆。胆胰。
// future 任務(wù)完成了!
// 普通任務(wù)完成了!
// 普通任務(wù)開始了。刻获。蜀涨。
// 普通任務(wù)開始了。蝎毡。厚柳。
// 等待子線程完成任務(wù)。沐兵。别垮。
// 普通任務(wù)被打斷了!!
// 普通任務(wù)被打斷了!!
// 任務(wù)都完成了,退出程序扎谎!

\color{red}{方法實(shí)例:}

isAlive() 方法

public final boolean isAlive()

描述

測(cè)試線程是否處于活動(dòng)狀態(tài)碳想。如果線程已啟動(dòng)烧董,但尚未終止,則它是活動(dòng)的胧奔。

參數(shù)

返回值

如果此線程是活動(dòng)的逊移,則為true;否則為false龙填。

public class TestThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            printMsg();
        }
    }

    /**
     * 打印當(dāng)前線程
     */
    public void printMsg() {
        Thread t = Thread.currentThread();
        String name = t.getName();
        System.out.println("name=" + name);
    }

    public static void main(String[] args) {
        TestThread tt = new TestThread();
        tt.setName("Thread");
        System.out.println("執(zhí)行 start()方法 之前, tt.isAlive()=" + tt.isAlive());
        tt.start();
        System.out.println("執(zhí)行 start()方法 之后, tt.isAlive()=" + tt.isAlive());
        for (int i = 0; i < 5; i++) {
            tt.printMsg();
        }
        System.out.println("main() 方法結(jié)束, tt.isAlive()=" + tt.isAlive());
    }
}

// 以上程序執(zhí)行結(jié)果為:
// 執(zhí)行 start()方法 之前, tt.isAlive()=false
// 執(zhí)行 start()方法 之后, tt.isAlive()=true
// name=main
// name=main
// name=main
// name=main
// name=main
// main() 方法結(jié)束, tt.isAlive()=true
// name=Thread
// name=Thread
// name=Thread
// name=Thread
// name=Thread

getState() 方法

public State getState()

描述

返回此線程的狀態(tài)胳泉。此方法設(shè)計(jì)用于監(jiān)測(cè)系統(tǒng)狀態(tài),而不是用于同步控制岩遗。

參數(shù)

返回值

這個(gè)線程的狀態(tài)扇商。

注意

線程狀態(tài)分為:

  • NEW -- 尚未啟動(dòng)的線程的線程狀態(tài)。
  • RUNNABLE -- 可運(yùn)行線程的線程狀態(tài)喘先。處于可運(yùn)行狀態(tài)的線程正在Java虛擬機(jī)中執(zhí)行钳吟,但它可能正在等待來自操作系統(tǒng)(如處理器)的其他資源。
  • BLOCKED -- 被阻止等待監(jiān)視器鎖定的線程的線程狀態(tài)窘拯。處于阻塞狀態(tài)的線程正在等待監(jiān)視器鎖進(jìn)入同步的塊/方法红且,或者在調(diào)用 Object.wait 后重新進(jìn)入同步塊/方法。
  • WAITING -- 等待線程的線程狀態(tài)涤姊。由于調(diào)用以下方法之一暇番,線程處于等待狀態(tài):未設(shè)置超時(shí)時(shí)間的 Object.wait()、未設(shè)置超時(shí)時(shí)間的 Thread.join()思喊、LockSupport.park壁酬。處于等待狀態(tài)的線程正在等待另一個(gè)線程執(zhí)行特定操作。
  • TIMED_WAITING -- 具有指定等待時(shí)間的等待線程的線程狀態(tài)恨课。由于使用指定的正等待時(shí)間調(diào)用以下方法之一舆乔,線程處于定時(shí)等待狀態(tài):Thread.sleep、設(shè)置超時(shí)時(shí)間的 Object.wait()剂公、設(shè)置超時(shí)時(shí)間的 Thread.join()希俩、LockSupport.parkNanos、
    LockSupport.parkUntil纲辽。
  • TERMINATED -- 已終止線程的線程狀態(tài)颜武。線程已完成執(zhí)行。
public class TestThread extends Thread {
    boolean waiting= true;
    boolean ready= false;

    TestThread() {
    }

    public void run() {
        String thrdName = Thread.currentThread().getName();
        System.out.println(thrdName + " starting.");
        while(waiting) {
            System.out.println("waiting:" + waiting);
        }
        System.out.println("waiting...");
        startWait();
        try {
            Thread.sleep(1000);
        } catch(Exception exc) {
            System.out.println(thrdName + " interrupted.");
        }
        System.out.println(thrdName + " 停止.");
    }

    /**
     * 開始等待
     */
    synchronized void startWait() {
        try {
            // 讓當(dāng)前線程進(jìn)入等待狀態(tài)
            while(!ready) wait();
        } catch(InterruptedException exc) {
            System.out.println("wait() interrupted");
        }
    }

    /**
     * 通知結(jié)束線程等待
     */
    synchronized void notice() {
        ready = true;
        // 喚醒在該對(duì)象上等待的某個(gè)線程
        notify();
    }

    public static void main(String args[]) throws Exception {
        TestThread thrd = new TestThread();
        thrd.setName("MyThread #1");
        // MyThread #1Alive:=false State:=NEW
        showThreadStatus(thrd);
        thrd.start();
        Thread.sleep(50);
        // MyThread #1Alive:=true State:=RUNNABLE
        showThreadStatus(thrd);
        thrd.waiting = false;
        Thread.sleep(50);
        // MyThread #1Alive:=true State:=WAITING
        showThreadStatus(thrd);
        thrd.notice();
        Thread.sleep(50);
        // MyThread #1Alive:=true State:=RUNNABLE
        showThreadStatus(thrd);
        while(thrd.isAlive()) {
            System.out.println("alive");
        }
        // MyThread #1Alive:=false State:=TERMINATED
        showThreadStatus(thrd);
    }

    /**
     * 打印線程狀態(tài)
     * @param thrd Thread 對(duì)象
     */
    static void showThreadStatus(Thread thrd) {
        System.out.println(thrd.getName() + "Alive:=" + thrd.isAlive() + " State:=" + thrd.getState());
    }
}

// 以上程序執(zhí)行結(jié)果為:
// MyThread #1Alive:=false State:=NEW
// MyThread #1 starting.
// waiting:true
// 拖吼。鳞上。。
// MyThread #1Alive:=true State:=RUNNABLE
// waiting:true
// waiting:false
// waiting...
// MyThread #1Alive:=true State:=WAITING
// MyThread #1 停止.
// MyThread #1Alive:=false State:=TERMINATED

setPriority() 方法

public final void setPriority(int newPriority)

描述

更改此線程的優(yōu)先級(jí)吊档。

參數(shù)

  • newPriority -- 將此線程設(shè)置為的優(yōu)先級(jí)篙议。

返回值

public class TestThread extends Thread {
    private int countDown = 5;
    private volatile double d = 0;
    public TestThread(int priority) {
        setPriority(priority);
        start();
    }
    public String toString() {
        return super.toString() + ": " + countDown;
    }
    public void run() {
        while(true) {
            for(int i = 1; i < 100000; i++) {
                d = d + (Math.PI + Math.E) / (double) I;
            }
            System.out.println(this);
            if(--countDown == 0) {
                return;
            }
        }
    }
    public static void main(String[] args) {
        TestThread maxThread = new TestThread(Thread.MAX_PRIORITY);
        maxThread.setName("maxThread");
        for(int i = 0; i < 5; i++) {
            TestThread minThreadnew = new TestThread(Thread.MIN_PRIORITY);
            minThreadnew.setName("minThreadnew");
        }
    }
}

join() 方法

// 該方法有以下幾種語(yǔ)法格式:
public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
public final synchronized void join(long millis, int nanos) throws InterruptedException 

描述

運(yùn)行到該方法的代碼線程進(jìn)入阻塞狀態(tài),調(diào)用該方法的線程進(jìn)入運(yùn)行狀態(tài)怠硼,最長(zhǎng)阻塞時(shí)間為 millis 毫秒涡上。

參數(shù)

  • millis -- 等待的時(shí)間(以毫秒為單位)趾断。
  • nanos -- 額外的納秒等待(范圍0-999999)。設(shè)置后等待時(shí)間為 millis + nanos吩愧。

注意

  • 如果millis的值為負(fù)數(shù),或者nanos的值不在0-999999的范圍內(nèi)增显,拋出IllegalArgumentException雁佳。

  • 如果有線程中斷了當(dāng)前線程,拋出InterruptedException同云。當(dāng)拋出此異常時(shí)糖权,當(dāng)前線程的中斷狀態(tài)將被清除。

public class TestThread extends Thread {
    private int countDown = 2;
    private static int threadCount = 0;
    private Thread mainThread;

    /**
     * 構(gòu)造方法炸站,threadCount每次先進(jìn)行自增星澳,作為線程名
     */
    public TestThread(Thread mainTThread) {
        super("" + ++threadCount);
        this.mainThread = mainTThread;
        System.out.println("TestThread 的 start() 方法之前,主線程狀態(tài):" + this.mainThread.getState());
        start();
    }

    @Override
    public String toString() {
        return "#" + getName() + ": " + countDown;
    }

    @Override
    public void run() {
        System.out.println("TestThread 的 run() 方法執(zhí)行中旱易,主線程狀態(tài):" + this.mainThread.getState());
        while (true) {
            System.out.println(this);
            if (--countDown == 0)
                return;
            try {
                sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 2; i++) {
            // 主線程代碼運(yùn)行到 join() 方法后禁偎,會(huì)將主線程掛起,并等待 TestThread 運(yùn)行完畢后恢復(fù)運(yùn)行
            new TestThread(Thread.currentThread()).join();
            System.out.println("TestThread 執(zhí)行完成阀坏,主線程狀態(tài):" + Thread.currentThread().getState());
        }
    }
}

// 以上程序執(zhí)行結(jié)果為:
// TestThread 的 start() 方法之前如暖,主線程狀態(tài):RUNNABLE
// TestThread 的 run() 方法執(zhí)行中,主線程狀態(tài):WAITING
// #1: 2
// #1: 1
// TestThread 執(zhí)行完成忌堂,主線程狀態(tài):RUNNABLE
// TestThread 的 start() 方法之前盒至,主線程狀態(tài):RUNNABLE
// TestThread 的 run() 方法執(zhí)行中,主線程狀態(tài):WAITING
// #2: 2
// #2: 1
// TestThread 執(zhí)行完成士修,主線程狀態(tài):RUNNABLE

interrupt() 方法

public void interrupt()

描述

該方法可以中斷一個(gè)正在運(yùn)行的線程枷遂。若是該線程處于某種阻塞時(shí)被調(diào)用中斷方法中斷,那么并不是將該線程直接中斷棋嘲,而是中斷其阻塞狀態(tài)酒唉,這時(shí)通常會(huì)拋出中斷異常,通知應(yīng)用程序該線程的阻塞狀態(tài)被打斷封字。

參數(shù)

返回值

public class TestThread extends Thread {
    public void run() {
        try {
            sleep(50000);  // 延遲50秒
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }
    }

    public static void main(String[] args) throws Exception {
        Thread thread = new TestThread();
        thread.start();
        System.out.println("在50秒之內(nèi)按任意鍵中斷線程!");
        System.in.read();
        thread.interrupt();
        thread.join();
        System.out.println("線程已經(jīng)退出!");
    }
}

// 以上程序執(zhí)行結(jié)果為:
// 在50秒之內(nèi)按任意鍵中斷線程!
// 3
// sleep interrupted
// 線程已經(jīng)退出!

獲取所有線程

public class TestThread extends Thread {
    public static void main(String[] args) {
        TestThread t1 = new TestThread();
        t1.setName("thread1");
        t1.start();
        // 獲取此線程所屬的線程組
        ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
        // 返回此線程組及其子組中活動(dòng)線程數(shù)的估計(jì)值(不準(zhǔn)確)
        int noThreads = currentGroup.activeCount();
        Thread[] lstThreads = new Thread[noThreads];
        // 將此線程組及其子組中的每個(gè)活動(dòng)線程復(fù)制到指定的數(shù)組中
        currentGroup.enumerate(lstThreads);
        for (int i = 0; i < noThreads; i++) {
            System.out.println("線程號(hào):" + i + " = " + lstThreads[i].getName());
        }
    }
}

// 以上程序執(zhí)行結(jié)果為:
// 線程號(hào):0 = main
// 線程號(hào):1 = Monitor Ctrl-Break
// 線程號(hào):2 = thread1

// 以上程序執(zhí)行結(jié)果也可能為:
// 線程號(hào):0 = main
// 線程號(hào):1 = Monitor Ctrl-Break

getPriority() 方法

public final int getPriority()

描述

獲取此線程的優(yōu)先級(jí)黔州。

參數(shù)

返回值

返回此線程的優(yōu)先級(jí)。

public class TestThread  {
    /**
     * 創(chuàng)建 Runnable 實(shí)例
     * @return Runnable 實(shí)例
     */
    private static Runnable makeRunnable() {
        // 使用內(nèi)部類的一種簡(jiǎn)寫向上轉(zhuǎn)型創(chuàng)建 Runnable 實(shí)例
        Runnable r = new Runnable() {
            public void run() {
                for (int i = 0; i < 5; i++) {
                    Thread t = Thread.currentThread();
                    System.out.println("在 run() 方法中 - priority = "
                            + t.getPriority()+ ", name = " + t.getName());
                    try {
                        Thread.sleep(2000);
                    }
                    catch (InterruptedException x) {
                    }
                }
            }
        };
        return r;
    }

    public static void main(String[] args) {
        System.out.println("在 main() 方法中 - Thread.currentThread().getPriority() = " + Thread.currentThread().getPriority());
        System.out.println("在 main() 方法中 - Thread.currentThread().getName() = "+ Thread.currentThread().getName());
        Thread threadA = new Thread(makeRunnable(), "threadA");
        threadA.setPriority(Thread.MAX_PRIORITY);
        threadA.start();
        try {
            Thread.sleep(3000);
        }
        catch (InterruptedException x) {
        }
        System.out.println("在 main() 方法中 - threadA.getPriority() = "+ threadA.getPriority());
    }
}

// 以上程序執(zhí)行結(jié)果為:
// 在 main() 方法中 - Thread.currentThread().getPriority() = 5
// 在 main() 方法中 - Thread.currentThread().getName() = main
// 在 run() 方法中 - priority = 10, name = threadA
// 在 run() 方法中 - priority = 10, name = threadA
// 在 main() 方法中 - threadA.getPriority() = 10
// 在 run() 方法中 - priority = 10, name = threadA
// 在 run() 方法中 - priority = 10, name = threadA
// 在 run() 方法中 - priority = 10, name = threadA
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末阔籽,一起剝皮案震驚了整個(gè)濱河市流妻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌笆制,老刑警劉巖绅这,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異在辆,居然都是意外死亡证薇,警方通過查閱死者的電腦和手機(jī)度苔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浑度,“玉大人寇窑,你說我怎么就攤上這事÷嵴牛” “怎么了甩骏?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)先慷。 經(jīng)常有香客問我饮笛,道長(zhǎng),這世上最難降的妖魔是什么论熙? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任福青,我火速辦了婚禮,結(jié)果婚禮上脓诡,老公的妹妹穿的比我還像新娘无午。我一直安慰自己,他們只是感情好誉券,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布指厌。 她就那樣靜靜地躺著,像睡著了一般踊跟。 火紅的嫁衣襯著肌膚如雪踩验。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天商玫,我揣著相機(jī)與錄音箕憾,去河邊找鬼。 笑死拳昌,一個(gè)胖子當(dāng)著我的面吹牛袭异,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播炬藤,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼御铃,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了沈矿?” 一聲冷哼從身側(cè)響起上真,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎羹膳,沒想到半個(gè)月后睡互,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年就珠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寇壳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡妻怎,死狀恐怖壳炎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情逼侦,我是刑警寧澤冕广,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布璃饱,位于F島的核電站慧妄,受9級(jí)特大地震影響屡江,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜涕滋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挠阁。 院中可真熱鬧宾肺,春花似錦、人聲如沸侵俗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)隘谣。三九已至增拥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間寻歧,已是汗流浹背掌栅。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留码泛,地道東北人猾封。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像噪珊,于是被迫代替她去往敵國(guó)和親晌缘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

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