關于Thread你所必須要知道的一切

線程是進程中辛勤勞作者,所以用好多線程就顯得十分必要

創建線程

  1. 重寫run方法,創建Thread對象start起來

    new Thread(){
        @Override
        public void run() {
            super.run();
            System.out.println("Thread run");
        };
    }.start();
    
  2. 用Thread的構造器Thread(Runnable target)創建出一個Thread對象,start起來。

    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Runnable run");
        };
    }).start();
    

那么,問題來了,如果有一哥們,既要重寫Thread類中的run方法,又要用構造器Thread(Runnable target)來創建Thread對象會發生什么事呢?
new Thread(new Runnable() { @Override public void run() { System.out.println("Runnable run"); } }){ @Override public void run() { super.run(); System.out.println("Thread run"); } }.start();
不用多說看,運行結果
Runnable run Thread run
兩個都會執行哦,為什么呢?答案就在super.run()
@Override public void run() { if (target != null) { target.run(); } }
target是什么?,target就是Thread(Runnable target)傳進來的Runnable,也就是說,如果重寫了Thread的run方法,在run方法中有調用super.run()則會先執行Runnable中的run方法,再執行重寫run的super.run()之下的方法

線程的狀態


接下來我們分別來看看線程在運行的時候不同狀態的切換

臨時阻塞
  1. 線程一旦啟動run方法就會加入CPU執行隊列中,進入等待CPU執行的狀態.這個時候如果CPU沒有執行該線程而去執行其他任務,則該線程進入了臨時阻塞狀態,如果CPU的執行權切換到該線程,則繼續執行run方法。
  2. 還可以在得到執行權的時候調用Thread.yeild()語句,釋放執行權。

凍結

  1. Thread#sleep()方法:這個方法會使線程凍結,如果執行到的代碼塊具有原子性,凍結的時候不會釋放鎖。sleep時間到,則線程回到運行狀態。
  2. Thread#suspend()方法和Thread#resume()方法:當線程調用suspend方法時,與sleep一樣,但需要用resume來喚醒線程,目前該方法已棄用。容易造成死鎖,下面會介紹。
  3. Object#waite()方法和Object#notify()方法:這兩個方法時Object的方法,我們知道,代碼塊如果具備原子性,需要用一個鎖對象,而這個鎖對象可以是任意對象。所以線程在執行具有原子性的代碼塊時,可以調用Object類的wait()方法,來釋放該線程所持有的鎖,并且使線程凍結。如果需要喚醒線程,則需要讓其他線程執行原子性代碼塊時,調用鎖對象的notify()或者nofityAll()方法:notify表示隨機喚醒凍結線程池里面的任一線程,notifyAll表示喚醒凍結線程池中的所有線程。利用鎖對象的這兩個特性,可以用來設計多線程的同步機制,下面會介紹。需要注意的是,如果Object的這幾個方法不是作為鎖對象的時候調用就會java.lang.IllegalMonitorStateException

消亡

  1. run()方法結束,自然而然線程也就等待被回收了。可以使用volatile修飾的標記位。
  2. 使用stop()。確實能退出,但是不安全,官方已棄用。例如,當線程執行一段具有原子性的代碼,執行到一半的情況下退出,是可以釋放鎖,但是造成了原子性的代碼沒執行部分。stop的線程會拋error:java.lang.ThreadDeath,可用try run方法中的代碼塊來catch Throwable.
  3. 使用interrupt()方法,可分為兩種情況
    • 線程處于凍結狀態,例如使用了Thread.sleep()方法,如果sleep時間還沒到,這個時候調用Thread#interrupt()則會拋InterruptedException,然后繼續執行run方法直到退出。
    • run方法中使用while(true), 則這個標志位用isInterrupted()方法來得到。這個時候調用Thread#interrupt()Thread#isInterrupted()返回false,run方法結束

死鎖

范例

public class DeadLock {
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();

    public static void main(String[] args) {
        new MyThread1().start();
        new MyThread2().start();
    }

    private static class MyThread1 extends Thread {
        private String nameString = "MyThread1 ";

        @Override
        public void run() {
            while (true)
                synchronized (lock1) {//1
                    System.out.println(nameString + "get loack1");
                    synchronized (lock2) {//2
                        System.out.println(nameString + "get loack2");
                        System.out.println(nameString + "release loack2");
                    }
                    System.out.println(nameString + "release loack1");
                }
        }
    }

    private static class MyThread2 extends Thread {
        private String nameString = "MyThread2 ";

        @Override
        public void run() {
            while (true)
                synchronized (lock2) {//3
                    System.out.println(nameString + "get loack2");
                    synchronized (lock1) {//4
                        System.out.println(nameString + "get loack1");
                        System.out.println(nameString + "release loack1");
                    }
                    System.out.println(nameString + "release loack2");
                }
        }
    }
}

運行結果:

MyThread1 get loack1
MyThread1 get loack2
MyThread1 release loack2
MyThread1 release loack1
MyThread1 get loack1
MyThread2 get loack2

上面的打印怎么來的呢?首先MyThread1啟動,進入while(true)代碼塊,拿著lock1進入代碼塊1,接著又拿著lock2進入代碼塊2,代碼塊2執行完之后釋放lock2,代碼塊1也執行完了,釋放lock1,這時候兩個鎖lock1,lock2都沒有被任一線程持有,這個時候MyThread2啟動了,MyThread2也進入了while(true)代碼塊,到這一步,兩個線程同時死循環執行while(true)里面的代碼,那么,就存在,MyThread1持有lock1進入代碼塊1,MyThread2持有lock2進入代碼塊3,由于MyThread1要進入代碼塊2,需要持有lock2,但是lock2已經被MyThread2持有,所以MyThread1阻塞,等待lock2被釋放,而MyThread2要進入代碼塊4,需要持有lock1,但lock1被阻塞的MyThread1持有,由于沒有鎖lock1,所以MyThread2也阻塞了,兩個線程都因等待而阻塞,就算等待天荒地老,需要的鎖也不會到來,造成兩個線程都無法繼續運行下去,這就是死鎖。

死鎖的必要條件

  1. 互斥條件:一個資源每次只能被一個進程使用。
  2. 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
  3. 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
  4. 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系
    ps:出自《操作系統》

這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。

線程間的同步

主要就是用線程運行狀態與線程凍結兩個狀態的切換來控制線程間的同步,我們來做一道面試題:請用線程A,B,C三個線程來依次打印上午,中午,下午,各打印10次。這道題的解法就是線程間的同步機制。

public class ThreadDemo {
    /**
     * 控制哪個線程執行
     */
    private static int which = 0;
    public static void main(String[] args) {
        //分別創建三個線程
        MyThread[] threads = new MyThread[] { new MyThread("A", "早上"),
                new MyThread("B", "中午"), new MyThread("C", "晚上") };
        for (int i = 0; i < threads.length; i++) {
            //設置線程的編號
            threads[i].setIndex(i);
            //啟動線程
            threads[i].start();
        }
    }

    private static class MyThread extends Thread {
        /**
         * 線程名字
         */
        private String tName;
        /**
         * 需要打印的內容
         */
        private String mPString;
        /**
         * 線程編號
         */
        private int idx;

        public MyThread(String name, String string) {
            this.tName = name;
            this.mPString = string;
        }


        public void setIndex(int i) {
            this.idx = i;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                synchronized (MyThread.class) {
                    while (this.idx != which) {//如果到了我的編號,跳出while執行打印
                        try {
                            //如果不到我的編號,我就繼續睡覺
                            MyThread.class.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(this.tName + ":" + this.mPString + ":"
                            + (i + 1));
                    which = ++which % 3;//打印完畢,編號++
                    MyThread.class.notifyAll();//喚醒所有沉睡的線程
                }
            }
        }
    }
}

打印結果符合預期,開啟三個線程,瞬間走到了for循環內,這個時候三個線程要搶奪MyThread.class這個鎖,假如線程線程2搶到了,進到while中去發現,咦,which=0,我不是0,我把鎖扔了睡覺去了。這時候線程3有可能搶到鎖,但結果跟線程2一樣,只有線程1搶到鎖才會跳出while去執行打印,打印完畢,++which,然后喚醒所有沉睡的線程,這個時候,喚醒所有沉睡的線程,線程1同步代碼塊走完,又到了三個線程搶鎖的過程,只有序號對的線程才可以執行打印,所以就可以實現在指定情況下指定線程做指定任務,這就是線程的同步機制,從這里看以看出可以用鎖對象的notify,notifyAll,wait來進行線程間的通信。

還可以用JDK1.5提供的Lock來替代sychronized代碼塊,用Condition的await(凍結,釋放鎖),signal(喚醒)方法來替代Object#wait和Object#notify,效率更高,這邊就不介紹了。

如果僅僅針對這道題,還有更簡便的做法,修改MyThread#run里面內容如下:

for (int i = 0; i < 10; i++) {
    while (this.idx != which) {
        Thread.yield();//如果我不該執行就放棄執行權
    }
    System.out.println(this.tName + ":" + this.mPString + ":"
            + (i + 1));
    which = ++which % 3;
}

輸出的內容一樣.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容

  • 一:java概述:1,JDK:Java Development Kit,java的開發和運行環境,java的開發工...
    ZaneInTheSun閱讀 2,676評論 0 11
  • 本文主要講了java中多線程的使用方法、線程同步、線程數據傳遞、線程狀態及相應的一些線程函數用法、概述等。 首先講...
    李欣陽閱讀 2,476評論 1 15
  • Java多線程學習 [-] 一擴展javalangThread類 二實現javalangRunnable接口 三T...
    影馳閱讀 2,975評論 1 18
  • 下面是我自己收集整理的Java線程相關的面試題,可以用它來好好準備面試。 參考文檔:-《Java核心技術 卷一》-...
    阿呆變Geek閱讀 14,884評論 14 507
  • 一、進程和線程 進程 進程就是一個執行中的程序實例,每個進程都有自己獨立的一塊內存空間,一個進程中可以有多個線程。...
    阿敏其人閱讀 2,620評論 0 13