天天看點

深入了解多線程(三)

深入了解多線程(三)

在前兩篇部落格中深入了解多線程(一)、深入了解多線程(二)中分别介紹了:多線程在jvm記憶體圖中的狀态、多線程的兩種實作方式、多線程的安全;接下來介紹線程的狀态。

1 線程狀态概述

當線程被建立并啟動以後,它既不是一啟動就進入了執行狀态,也不是一直處于執行狀态。線上程的生命周期中,

有幾種狀态呢?在API中 java.lang.Thread.State 這個枚舉中給出了六種線程狀态:

這裡先列出各個線程狀态發生的條件,下面将會對每種狀态進行詳細解析

線程狀态 導緻狀态發生條件
New(建立) 線程剛被建立,但是并未啟動。還沒調用start方法
Runable(可運作) 線程可以在java虛拟機中運作的狀态,可能正在運作自己代碼,也可能沒有,這取決于作業系統處理器
Blocked(鎖阻塞) 當一個線程試圖擷取一個對象鎖,而該對象鎖被其他的線程持有,則該線程進入Blocked狀态;當該線程持有鎖時,該線程将變成Runable狀态
Waiting(無限等待) 一個線程在等待另一個線程執行一個(喚醒)動作時,該線程進入Waiting狀态。進入這個 狀态後是不能自動喚醒的,必須等待另一個線程調用notify或者notifyAll方法才能夠喚醒
Timed Waiting(計時等待) 同waiting狀态,有幾個方法有逾時參數,調用他們将進入Timed Waiting 狀态。這一狀态将一直保持到朝時期滿或者接受到喚醒通知。帶有逾時參數的常用方法有Thread.sleep、Object.wait
Teminated(被終止) 因為run方法正常退出而死亡,或者因為沒有捕獲的異常終止了run方法而死亡

這裡不再研究這幾種狀态的實作原理,而是研究這幾種狀态的轉換問題。

1.1 Timed Waiting(計時等待)

這裡的計時等待,其實是顯而易見的,我們平常寫代碼的時候也經常用到,就是Thread.sleep(),其實 當我們調用了sleep方法後,目前執行的線程就進入到“休眠狀态”,其實就是所謂的Timed Waiting ,接下來将通過一個案例加深對該狀态的一個了解。

public class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("i:"+i);
        }
    }
}
           

通過案例可以發現,sleep方法的使用還是很簡單的。我們需要記住下面幾點:

  1. 進入 TIMED_WAITING 狀态的一種常見情形是調用的 sleep 方法,單獨的線程也可以調用,不一定非要有協

作關系。

  1. 為了讓其他線程有機會執行,可以将Thread.sleep()的調用放線程run()之内。這樣才能保證該線程執行過程

中會睡眠

  1. sleep與鎖無關,線程睡眠到期自動蘇醒,并傳回到Runnable(可運作)狀态。
小提示:sleep()中指定的時間是線程不會運作的最短時間。是以,sleep()方法不能保證該線程睡眠到期後就開始立刻執行。

Timed Waiting 線程狀态圖:

深入了解多線程(三)

1.2 BLOCKED(鎖狀态)

Blocked狀态在API中的介紹為:一個正在阻塞等待一個螢幕鎖(鎖對象)的線程處于這一狀态。

知道了多線程的同步機制,那麼這個狀态是非常好了解的了。比如,線程A與線程B代碼中使用同一鎖,如果線程A獲 取到鎖,線程A進入到Runnable狀态,那麼線程B就進入到Blocked鎖阻塞狀态。

這是由Runnable狀态進入Blocked狀态。除此Waiting以及Time Waiting狀态也會在某種情況下進入阻塞狀态

深入了解多線程(三)

1.3 Waiting (無限等待)

Wating狀态在API中介紹為:一個正在無限期等待另一個線程執行一個特别的(喚醒)動作的線程處于這一狀态。

無限等待一般情況下是很少遇到的,但并不妨礙我們進行一個簡單深入的了解。我們通過一段代碼來學習一下:

public class MyThread{

    public static Object object=new Object();

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (object){
                        System.out.println(Thread.currentThread().getName()+"擷取到鎖對象,調用wait方法,進入waiting,釋放鎖對象");
                        try {
                            //無限等待
                            object.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"從waiting狀态醒來,擷取鎖對象,繼續執行");
                    }
                }
            }
        },"等待").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println(Thread.currentThread().getName()+"等待3秒鐘");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (object){
                        System.out.println(Thread.currentThread().getName()+"擷取鎖對象,調用notify方法,釋放鎖對象");

                        object.notify();
                    }
                }
            }
        },"喚醒").start();
    }
}
           

通過上述案例我們會發現,一個調用了某個對象的 Object.wait 方法的線程會等待另一個線程調用此對象的

Object.notify()方法 或 Object.notifyAll()方法。

其實waiting狀态并不是一個線程的操作,它展現的是多個線程間的通信,可以了解為多個線程之間的協作關系,

多個線程會争取鎖,同時互相之間又存在協作關系。就好比在公司裡你和你的同僚們,你們可能存在晉升時的競

争,但更多時候你們更多是一起合作以完成某些任務。

當多個線程協作時,比如A,B線程,如果A線程在Runnable(可運作)狀态中調用了wait()方法那麼A線程就進入

了Waiting(無限等待)狀态,同時失去了同步鎖。假如這個時候B線程擷取到了同步鎖,在運作狀态中調用了

notify()方法,那麼就會将無限等待的A線程喚醒。注意是喚醒,如果擷取到鎖對象,那麼A線程喚醒後就進入

Runnable(可運作)狀态;如果沒有擷取鎖對象,那麼就進入到Blocked(鎖阻塞狀态)。

深入了解多線程(三)

總結:線程狀态轉換圖

深入了解多線程(三)

Timed Waiting(計時等待) 與 Waiting(無限等待) 狀态聯系還是很緊密的,

比如Waiting(無限等待) 狀态中wait方法是空參的,而timed waiting(計時等待) 中wait方法是帶參的。

這種帶參的方法,其實是一種倒計時操作,相當于我們生活中的小鬧鐘,我們設定好時間,到時通知,可是 如果提前得到(喚醒)通知,那麼設定好時間在通知也就顯得多此一舉了,那麼這種設計方案其實是一舉兩得。如果沒有得到(喚醒)通知,那麼線程就處于Timed Waiting狀态,直到倒計時完畢自動醒來;如果在倒計時期間得到(喚醒)通知,那麼線程從Timed Waiting狀态立刻喚醒。