java學習總結之線程

前言

在java中,線程非常重要,我們要分清楚進程和線程的區別:進程是操作系統中資源分配的基本單位,進程是指一個內存中運行的應用程序,每個進程都擁有自己的一塊獨立的內存空間,進程之間的資源不共享;而線程是CPU調度的最小單元,一個進程可以有多個線程,線程之間的堆空間是共享的,但棧空間是獨立的,java程序的進程至少包含主線程和後臺線程(垃圾回收線程)。瞭解這些知識後,來看下文有關線程的知識。

一、併發和並行

我們先來看一下概念:

  • 並行:指兩個或多個事件在同一時刻點發生
  • 併發:指兩個或多個事件在同一時間段內發生

對於單核CPU的計算機來說,它是不能並行的處理多個任務,它的每一時刻只能有一個程序執行時間片(時間片是指CPU分配給各個程序的運行時間),故在微觀上這些程序只是分時交替的運行,所以在宏觀看來在一段時間內有多個程序在同時運行,看起來像是並行運行。

對於多核CPU的計算機來說,它就可以並行的處理多個任務,可以做到多個程序在同一時刻同時運行。

同理對線程也一樣,但系統只有一個CPU時,線程會以某種順序執行,我們把這種情況稱爲線程調度,所以從宏觀角度上看線程是並行運行的,但是從微觀角度來看,卻是串行運行,即一個線程一個線程的運行。

一般來說,JVM的進程和線程都是與操作系統的進程和線程一 一對應的,這樣做的好處是可以使操作系統來調度進程和線程,進程和線程調度是操作系統的核心模塊,它的實現是非常複雜的,特別是考慮到多核的情況,因此,就完全沒有必要在JVM中再提供一個進程和線程調度機制。

二、線程的創建與啓動

有3種方式使用線程。

方式1:繼承Thread類

定義一個類繼承java.lang.Thread類,重寫Thread類中的run方法,如下:

public class MyThread extends Thread {
    public void run() {
        // ...
    }
}

//使用線程
public static void main(String[] args) {
    Thread thread = new MyThread();
     thread.start();
}

方式2:實現Runnable接口

2.1:定義一個類實現Runnable接口

實現 Runnable只能當做一個可以在線程中運行的任務,不是真正意義上的線程,因此最後還需要通過 Thread 來調用,如下:

public class MyRunnable implements Runnable {
    public void run() {
        // ...
    }
}

//使用線程
public static void main(String[] args) {
    MyRunnable instance = new MyRunnable();
    Thread thread = new Thread(instance);
    thread.start();
}

2.2、使用匿名內部類

這種方式只適用於這個線程只使用一次的情況,如下:

public class MyRunnable implements Runnable {

//使用線程
public static void main(String[] args) {
    new Thread(new Runnable(){
        public void run(){
            // ...
        }
    }).start();
}

方式3:實現Callable接口

與 Runnable 相比,Callable 可以有返回值,返回值通過 FutureTask 進行封裝,所以在創建Thread時,要把FutureTask 傳進去,如下:

public class MyCallable implements Callable<Integer> {
    public Integer call() {
        return 123;
    }
}

//使用線程
public static void main(String[] args) throws ExecutionException, InterruptedException {
    MyCallable mc = new MyCallable();
    FutureTask<Integer> ft = new FutureTask<>(mc);
    Thread thread = new Thread(ft);
    thread.start();
    System.out.println(ft.get());
}

繼承與實現的區別

1、繼承方式:

(1)java中類是單繼承的,如果繼承了Thread,該類就不能有其他父類了,但是可以實現多個接口

(2)從操作上分析,繼承方式更簡單,獲取線程名字也簡單

2、實現方式:

(1)java中類可以實現多接口,此時該類還可以繼承其他類,並且還可以實現其他接口

(2)從操作上分析,實現方式稍複雜,獲取線程名字也比較複雜,得通過Thread.currentThread來獲取當前線程得引用

綜上所述,實現接口會更好一些。

三、線程的中斷與終止

1、interrupt()、isInterrupted()、interrupted()的作用

中斷就是線程的一個標識位,它表示一個運行中的線程是否被其他線程調用了中斷操作,其他線程可以通過調用線程的interrupt()方法對其進行中斷操作,線程可以通過調用isInterrupted()方法判斷是否被中斷,線程也可以通過調用Thread的interrupted()靜態方法對當前線程的中斷標識位進行復位。

大家不要認爲調用了線程的interrupt()方法,該線程就會停止,它只是做了一個標誌位,如下:

public class InterruptThread extends Thread{
    @Override
    public void run() {
        //一個死循環
        while (true){
            System.out.println("InterruptThread正在執行");
        }
    }
}

public static void main(String[] args) throws InterruptedException {
    InterruptThread interruptThread = new InterruptThread();
    interruptThread.start();
    interruptThread.interrupt();//調用線程的interrupt()
    System.out.println("interruptThread是否被中斷,interrupt  = " + interruptThread.isInterrupted());//此時isInterrupted()方法返回true
}

輸出結果:
interruptThread是否被中斷,interrupt  = true
InterruptThread正在執行
InterruptThread正在執行
InterruptThread正在執行
//...

可以看到當你調用了線程的interrupt()方法後,此時調用isInterrupted()方法會返回true,但是該線程還是會繼續執行下去。所以怎麼樣才能終止一個線程的運行呢?

2、終止線程的運行

一個線程正常執行完run方法之後會自動結束,如果在運行過程中發生異常也會提前結束;所以利用這兩種情況,我們還可以通過以下三種種方式安全的終止運行中的線程:

2.1、利用中斷標誌位

前面講到的中斷操作就可以用來取消線程任務,如下:

public class InterruptThread extends Thread{
    @Override
    public void run() {
        while (!isInterrupted()){//利用中斷標記位
            System.out.println("InterruptThread正在執行");
        }
    }
}

當不需要運行InterruptThread線程時,通過調用InterruptThread.interrupt()使得isInterrupted()返回true,就可以讓線程退出循環,正常執行完畢之後自動結束。

2.2、利用一個boolean變量

利用一個boolean變量和上述方法同理,如下:

public class InterruptThread extends Thread{
    
    private volatile boolean isCancel;

    @Override
    public void run() {
        while (!isCancel){//利用boolean變量
            System.out.println("InterruptThread正在執行");
        }
    }

    public void cancel(){
        isCancel = true;
    }
}

當不需要運行InterruptThread線程時,通過調用InterruptThread.cancel()使isCancel等於true,就可以讓線程退出循環,正常執行完畢之後自動結束,這裏要注意boolean變量要用volatile修飾保證內存的可見性。

2.3、響應InterruptedException

通過調用一個線程的 interrupt() 來中斷該線程時,如果該線程處於阻塞、限期等待或者無限期等待狀態,那麼就會拋出 InterruptedException,從而提前結束該線程,例如當你調用Thread.sleep()方法時,通常會讓你捕獲一個InterruptedException異常,如下:

public class InterruptThread extends Thread{
    @Override
    public void run() {
        try{
            while (true){
                Thread.sleep(100);//Thread.sleep會拋出InterruptedException
                System.out.println("InterruptThread正在執行");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

當不需要運行InterruptThread線程時,通過調用InterruptThread.interrupt()使得 Thread.sleep() 拋出InterruptedException,就可以讓線程退出循環,提前結束。在拋出InterruptedException異常之前,JVM會把中斷標識位復位,此時調用線程的isInterrupted()方法將會返回false。

四、線程的生命週期

1、線程的6種狀態

線程也是有生命週期,也就是存在不同的狀態,狀態之間相互轉換,線程可以處於以下的狀態之一:

1.1、NEW(新建狀態)

使用new創建一個線程對象,但還沒有調用線程的start方法,Thread t = new Thread(),此時屬於新建狀態。

1.2、RUNNABLE(可運行狀態)

但在新建狀態下線程調用了start方法,t.start(),此時進入了可運行狀態。可運行狀態又分爲兩種狀態:

  • ready(就緒狀態):線程對象調用stat方法後,等待JVM的調度,此時線程並沒有運行。
  • running(運行狀態):線程對象獲得JVM調度,此時線程開始運行,如果存在多個CPU,那麼允許多個線程並行運行。

線程的start方法只能調用一次,否則報錯(IllegalThreadStateException)。

1.3、BLOCKED(阻塞狀態)

正在運行的線程因爲某些原因放棄CPU,暫時停止運行,就會進入阻塞狀態,此時JVM不會給該線程分配CPU,直到線程重新進入就緒狀態,纔有機會轉到運行狀態,阻塞狀態只能先進入就緒狀態,不能跳過就緒狀態直接進入運行狀態。線程進入阻塞狀態常見的情況有:

  • 1、當A線程處於運行狀態時,試圖獲取同步鎖,卻被B線程獲取,此時JVM把當前A線程放到對象的鎖池(同步隊列)中,A線程進入阻塞狀態,等待獲取對象的同步鎖。
  • 2、當線程處於運行狀態時,發出了IO請求,此時進入阻塞狀態。

如果是使用Synchronize關鍵字,那麼嘗試獲取鎖的線程會進入BLOCKED狀態;如果是使用java.util.concurrent 類庫中的Lock,那麼嘗試獲取鎖的線程則會進入WAITING或TIMED WAITING狀態,因爲java.util.concurrent 類庫中的Lock是使用LockSupport來進行同步的。

1.4、WAITING(等待狀態)

正在運行的線程調用了無參數的wait方法,此時JVM把該線程放入對象的等待池(等待隊列)中,此時線程進入等待狀態,等待狀態的線程只能被其他線程喚醒,否則不會被分配 CPU 時間片。下面是讓線程進入等待狀態的方法:

進入方法 退出方法
無Timeout參數的Object.wait() Object.notify() / Object.notifyAll()
無Timeout參數的Thread.join() 方法 被調用的線程執行完畢
LockSupport.park() 方法 LockSupport.unpark(Thread)

1.5、TIMED WAITING(計時等待狀態)

正在運行的線程調用了有參數的wait方法,此時JVM把該線程放入對象的等待池中,此時線程進入計時等待狀態,計時等待狀態的線程被其它線程顯式地喚醒,在一定時間之後會被系統自動喚醒。下面是讓線程進入等待狀態的方法:

進入方法 退出方法
調用Thread.sleep(int timeout) 方法 時間結束
有Timeout 參數的 Object.wait() 方法 時間結束 / Object.notify() / Object.notifyAll()
有Timeout 參數的 Thread.join() 方法 時間結束 / 被調用的線程執行完畢
LockSupport.parkNanos() 方法 LockSupport.unpark(Thread)
LockSupport.parkUntil() 方法 LockSupport.unpark(Thread)

阻塞和等待的區別在於,阻塞是被動的,它是在等待獲取一個排它鎖。而等待是主動的,通過調用 Thread.sleep() 和 Object.wait() 等方法進入。

1. 6、TREMINATED(終止狀態)

又稱死亡狀態,表示線程的終止。線程進入終止狀態的情況有:

  • 1、正常執行完run方法,線程正常退出。
  • 2、遇到異常而退出

線程一旦終止了,就不能再次啓動,否則報錯(IllegalThreadStateException)

2、線程的狀態轉換圖

六、線程之間的通信

如果一個線程從頭到尾的執行完一個任務,不需要和其他線程打交道的話,那麼就不會存在安全性問題了,由於java內存模式的存在,如下:

每一個java線程都有自己的工作內存,線程之間要想協作完成一個任務,就必須通過主內存來通信,所以這裏就涉及到對共享資源的競爭,在主內存中的東西都是線程之間共享,所以這裏就必須通過一些手段來讓線程之間完成正常通信。主要有以下兩種方法:

1、wait() / notify() notifyAll() 機制

它們都是Object類中的方法,它們的主要作用如下:

  • wait():執行該方法的線程對象釋放同步鎖(這是因爲,如果沒有釋放鎖,那麼其它線程就無法進入對象的同步方法或者同步控制塊中,那麼就無法執行 notify() 或者 notifyAll() 來喚醒掛起的線程,造成死鎖),然後JVM把該線程存放在等待池中,等待其他線程喚醒該線程
  • notify():執行該方法的線程喚醒在等待池中等待的任意一個線程,把線程轉到鎖池中等待
  • notifyAll():執行該方法的線程喚醒在等待池中等待的所有線程,把線程轉到鎖池中等待

注意:上述方法只能在同步方法或者同步代碼中使用,否則會報IllegalMonitorStateException異常,還有上述方法只能被同步監聽鎖對象來調用,不能使用其他對象調用,否則會報IllegalMonitorStateException異常。

假設A線程和B線程共同操作一個X對象,A、B線程可以通過X對象的wait方法和notify方法進行通信,流程如下:

1、當A線程執行X對象的同步方法時,A線程持有X對象的鎖,則B線程沒有執行同步方法的機會,B線程在X對象的鎖池中等待。

2、A線程在同步方法中執行X.wait()時,A線程釋放X對象的鎖,進入X對象的等待池中。

3、在X對象的鎖池中等待獲取鎖的B線程在這時獲取X對象的鎖,執行X對象的另一個同步方法。

4、B線程在同步方法中執行X.notify()或notifyAll()時,JVM把A線程從X對象的等待池中移到X對象的鎖池中,等待獲取鎖。

5、B線程執行完同步方法,釋放鎖,A線程獲取鎖,從上次停下來的地方繼續執行同步方法。

下面以一個ATM機存錢取錢的例子說明,ATM機要在銀行把錢存進去後,其他人才能取錢,如果沒錢取,只能先回家等待,等銀行通知你有錢取了,再來取,如果有錢取,就直接取錢。

ATM機,存錢和取錢方法都是同步方法:

public class ATM {

    private int money;
    private boolean isEmpty = true;//標誌ATM是否有錢的狀態

    /**
     * 往ATM機中存錢
     */
    public synchronized void push(int money){

        try{
            //ATM中有錢,等待被人把錢取走
            while (!isEmpty){
                this.wait();
            }

            //ATM中沒錢了,開始存錢
            System.out.println(Thread.currentThread().getName() + ":" + "發現ATM機沒錢了,存錢中...");
            Thread.sleep(2000);
            this.money = money;
            System.out.println(Thread.currentThread().getName() + ":" + "存錢完畢,存了" + money + "元");

            //存錢完畢,把標誌置爲false
            isEmpty = false;

            //ATM中有錢了,通知別人取錢
            this.notify();

        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

    /**
     * 從ATM機中取錢
     */
    public synchronized void pop(){
            try {

                //ATM中沒錢取,等待通知
                while (isEmpty){
                    System.out.println(Thread.currentThread().getName() + ":" + "ATM機沒錢,等待中...");
                    this.wait();
                }

                //ATM中有錢了,開始取錢
                System.out.println(Thread.currentThread().getName() + ":" + "收到通知,取錢中...");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + ":"+ "取出完畢,取出了" + this.money + "錢");

                //取錢完畢,把標誌置爲true
                isEmpty = true;

                //ATM沒錢了,通知銀行存錢
                this.notify();

            } catch (InterruptedException e) {
                e.printStackTrace();
        }
    }

}

銀行, 需要傳入同一個ATM示例:

public class Blank implements  Runnable  {

    private ATM mAtm;//共享資源

    public Blank(ATM atm){
        this.mAtm = atm;
    }

    @Override
    public void run() {
        //銀行來存錢
        for(int i = 0; i < 2; i++){
            mAtm.push(100);
        }
    }
}

小明, 需要傳入同一個ATM示例:

public class Person implements Runnable{

    private ATM mAtm;//共享資源

    public Person(ATM atm){
        this.mAtm = atm;
    }

    @Override
    public void run() {
        //這個人來取錢
        mAtm.pop();
    }
}

客戶端操作,我特地讓小明提前來取錢,此時ATM機中是沒錢的,小明要等待:

 public static void main(String[] args){

        //創建一個ATM機
        ATM atm = new ATM();
        //小明來取錢
        Thread tPerson = new Thread(new Person(atm), "XiaoMing");
        tPerson.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //銀行來存錢
        Thread tBlank = new Thread(new Blank(atm), "Blank");
        tBlank.start();
    }

輸出結果:

XiaoMing:ATM機沒錢,等待中...
Blank:發現ATM機沒錢了,存錢中...
Blank:存錢完畢,存了100元
XiaoMing:收到通知,取錢中...
XiaoMing:取出完畢,取出了100錢
Blank:發現ATM機沒錢了,存錢中...
Blank:存錢完畢,存了100

可以看到,小明總是在收到ATM的通知後纔來取錢,如果通過這個存錢取錢的例子還不瞭解wait/notify機制的話,可以看看這個修廁所的例子

wait() 和 sleep() 的區別是什麼,首先wait()是Object的方法,而sleep()是Thread的靜態方法,其次調用wait()會釋放同步鎖,而sleep()不會,最後一點不同的是調用wait方法需要先獲得鎖,而調用sleep方法是不需要的。

2、await() / signal() signalAll()機制

從java5開始,可以使用Lock機制取代synchronized代碼塊和synchronized方法,使用java.util.concurrent 類庫中提供的Condition 接口的await / signal() signalAll()方法取代Object的wait() / notify() notifyAll() 方法。

下面使用Lock機制和Condition 提供的方法改寫上面的那個例子,如下:

ATM2:

public class ATM2 {

    private int money;
    private boolean isEmpty = true;//標誌ATM是否有錢的狀態

    private Lock mLock = new ReentrantLock();//新建一個lock
    private Condition mCondition = mLock.newCondition();//通過lock的newCondition方法獲得一個Condition對象

    /**
     * 往ATM機中存錢
     */
    public void push(int money){
        mLock.lock();//獲取鎖
        try{
            //ATM中有錢,等待被人把錢取走
            while (!isEmpty){
                mCondition.await();
            }

            //ATM中沒錢了,開始存錢
            System.out.println(Thread.currentThread().getName() + ":" + "發現ATM機沒錢了,存錢中...");
            Thread.sleep(2000);
            this.money = money;
            System.out.println(Thread.currentThread().getName() + ":" + "存錢完畢,存了" + money + "元");

            //存錢完畢,把標誌置爲false
            isEmpty = false;

            //ATM中有錢了,通知別人取錢
            mCondition.signal();

        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            mLock.unlock();//釋放鎖
        }

    }

    /**
     * 從ATM機中取錢
     */
    public void pop(){
        mLock.lock();//獲取鎖
        try {

            //ATM中沒錢取,等待通知
            while (isEmpty){
                System.out.println(Thread.currentThread().getName() + ":" + "ATM機沒錢,等待中...");
                 mCondition.await();
            }

            //ATM中有錢了,開始取錢
            System.out.println(Thread.currentThread().getName() + ":" + "收到通知,取錢中...");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + ":"+ "取出完畢,取出了" + this.money + "錢");

            //取錢完畢,把標誌置爲true
            isEmpty = true;

            //ATM沒錢了,通知銀行存錢
            mCondition.signal();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            mLock.unlock();//釋放鎖
        }
    }
}

可以看到ATM2改寫ATM後,把方法的synchronized去掉,因爲Lock機制沒有同步鎖的概念,然後獲取lock鎖,在finally裏釋放lock鎖,還把原本Object.wait()用Condition.await()代替,原本Object.notify()用Condition.signal()代替。

客戶端操作只是把ATM換成ATM2,輸出結果和上面的一樣,就不在累述。

3、死鎖

多線程通信的時候很容易造成死鎖,死鎖一旦發生,只能通過外力解決。

死鎖是什麼?

當A線程等待獲取由B線程持有的鎖,而B線程正在等待獲取由A線程持有的鎖,發生死鎖現象,JVM既不檢測也不會避免這種情況,所以程序員必須保證不導致死鎖。

官方定義:如果一組進程中的每一個進程都在等待僅由該進程組中的其他進程才能引發的事件,那麼該組進程就是死鎖。

產生死鎖的原因?

多個線程對不可搶佔性資源可消耗性資源的進行爭奪時,可能會產生死鎖。

產生死鎖的必要條件?

同時滿足以下4個條件,就會產生死鎖:

1、 互斥條件:線程對分配到的資源進行排他性使用;

2、 請求和保持條件:線程已經保持了至少一個資源,又提出了新的資源請求;

3、 不可搶佔條件: 線程已獲得的資源在未使用完之前不能被搶佔;

4、 循環等待條件:發生死鎖時,一定存在一個線程-資源的循環鏈。

如何預防死鎖?

在程序運行之前,可以通過以下3點來預防死鎖:

1、破壞必要條件中的一個或幾個就行;

2、當多個線程都要訪問共享資源A、B、C時,保證每一個線程都按照相同的順序去訪問去訪問他們,比如先訪問A,接着訪問B,最後訪問C;

3、不要使用Thread類中過時的方法,因爲容易導致死鎖,所以被廢棄,例如A線程獲得對象鎖,正在執行一個同步方法,如果B線程調用A線程的suspend(),此時A線程暫停運行,放棄CPU,但是不會放棄鎖,所以B就永遠不會得到A持有的鎖。

在操作系統中,還可以在程序運行時,通過銀行家算法來避免死鎖。

解決死鎖的辦法?

1、從一個或多個線程中,搶佔足夠數量的資源分配給死鎖線程,解決死鎖狀態;

2、終止或撤銷系統中一個或多個線程,直到打破死鎖狀態。

上面對死鎖討論的所有情況,同樣適用於進程,線程就是"“輕量級進程”"。

4、 Thread類中過時的方法

由於線程安全問題,被棄用,如下:

  • void suspend():暫停當前線程。
  • void resume():恢復當前線程。
  • void stop():結束當前線程

suspend()方法在調用之後不會釋放已經佔有的資源(鎖),然後進入睡眠狀態,這樣很容易導致死鎖; stop()方法直接終止線程,不會保證線程資源的正常釋放,導致程序處於不確定狀態。對於suspend()和 resume()可以用上面提到的等待/通知機制代替,而 stop()方法可以用上面提到的終止線程運行的3種方式代替。

七、線程的控制操作

下面來看一些可以控制線程的操作。

1、線程休眠

讓執行的線程暫停等待一段時間,進入計時等待狀態,使用如下:

public static void main(String[] args){
    Thread.sleep(1000);
}

調用sleep()後,當前線程放棄CPU,在指定的時間段內,sleep所在的線程不會獲得執行的機會,在此狀態下該線程不會釋放同步鎖。

2、聯合線程

在線程中調用另一個線程的 join() 方法,會將當前線程置於阻塞狀態,等待另一個線程完成後才繼續執行,原理就是等待/通知機制,使用如下:

public class JoinThread extends Thread {

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("JoinThread執行完畢!");
    }
}

 public static void main(String[] args) throws InterruptedException {
        JoinThread joinThread = new JoinThread();
        joinThread.start();
        System.out.println("主線程等待...");
        joinThread.join();//主線程等join線程執行完畢後才繼續執行
        System.out.println("主線程執行完畢");
    }

輸出結果:
主線程等待...
JoinThread執行完畢!
主線程執行完畢

對於以上代碼,主線程會等join線程執行完畢後才繼續執行,因此最後的結果能保證join線程的輸出先於主線程的輸出。

3、後臺線程

顧名思義,在後臺運行的線程,其目的是爲其他線程提供服務,也稱“守護線程”,JVM的垃圾回收線程就是典型的後臺線程,通過**t.setDaemon(true)**把一個線程設置爲後臺線程,如下:

public class DeamonThread extends Thread {
    @Override
    public void run() {
        System.out.println(getName());
    }
}

 public static void main(String[] args) throws InterruptedException {
        //主線程不是後臺線程,是前臺線程
        DeamonThread deamonThread = new DeamonThread();
        deamonThread.setDaemon(true);//設置子線程爲後臺線程
        deamonThread.start();
        //通過deamonThread.isDaemon()判斷是否是後臺線程
        System.out.println(deamonThread.isDaemon());
    }

輸出結果:true

後臺線程有以下幾個特點:

1、若所有的前臺線程死亡,後臺線程自動死亡,若前臺線程沒有結束,後臺線程是不會結束的。

2、前臺線程創建的線程默認是前臺線程,可以通過setDaemon(true)設置爲後臺線程,在後臺線程創建的新線程,新線程是後臺線程。

注意:t.setDaemon(true)方法必須在start方法前調用,否則會報IllegalMonitorStateException異常

4、線程優先級

當線程的時間片用完時就會發生線程調度,而線程優先級就是決定線程需要多或少分配一些CPU時間片的線程屬性,在java中,通過一個成員變量priority來控制優先級,在線程構建時可以通過setPriority(int)方法來修改線程的優先級,如下:

public class PriorityThread extends Thread{
    @Override
    public void run() {
        super.run();
    } 
}

public static void main(String[] args) throws InterruptedException {
    PriorityThread priorityThread = new PriorityThread();
    priorityThread.setPriority(Thread.MAX_PRIORITY);//10
    priorityThread.start();
}

優先級範圍從1到10,默認是5,優先級高的線程分配的時間片數量要多於優先級低的線程。

在不同的JVM以及操作系統上,線程優先級規劃會有差異,有些操作系統會忽略對線程優先級的設定,所以線程優先級不能作爲程序正確性的依賴保證,因爲操作系統可以完全不用理會線程優先級的設定

5、線程禮讓

對靜態方法 Thread.yield() 的調用,聲明瞭當前線程已經完成了生命週期中最重要的部分,可以切換給其它線程來執行。如下:

public class YieldThread extends Thread {
    @Override
    public void run() {
        System.out.println("已經完成重要部分,可以讓其他線程獲取CPU時間片");
        Thread.yield();
    }
}

該方法只是對線程調度器的一個建議,而且也只是建議具有相同優先級的其它線程可以運行。也就是說,就算你執行了這個方法,該線程還是有可能繼續運行下去。

6、線程組

java.lang.ThreadGroup類表示線程組,可以對一組線程進行集中管理,當用戶在創建線程對象時,可以通過構造器指定其所屬的線程組:Thread(ThreadGroup group, String name)。

如果A線程創建B線程,如果沒有設置B線程的分組,那麼B線程加入到A線程的線程組,一旦線程加入某個線程組,該線程就一直存在於該線程組中直到線程死亡,不能在中途修改線程的分組。

當java程序運行時,JVM會創建名爲main的線程組,在默認情況下,所以的線程都屬於該線程組。

結語

本文到這裏就結束了,在平時開發中我們一般都只會使用線程,但卻很少去了解線程的生命週期、通信機制等,但我們不要忽略掉這些知識點,它們都是面試常客,也是非常的重要,在java中,一般不推薦你直接new一個線程使用,如果你需要創建的線程數量非常多的話,這時就需要使用線程池來幫助你管理線程的創建,在線程的內部中,還有一個用於存儲數據的Map集合,java提供了一個ThreadLocal類來操作這些集合,ThreadLocal在多線程環境下可以很好的保證了這些數據只能爲本線程使用,從而避免了併發問題。

以上就是我對線程的總結,希望對大家有所幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章