java線程疑難點總結

1、java線程生命週期:

 

 

 

 

線程的5個狀態 

1、新建狀態(New):當線程對象對創建後,即進入了新建狀態,如:Thread t = new MyThread();

2、就緒狀態(Runnable):當調用線程對象的start()方法(t.start();),線程即進入就緒狀態。處於就緒狀態的線程,只是說明此線程已經做好了準備,隨時等待CPU調度執行,獲取cpu 的使用權,並不是說執行了t.start()此線程立即就會執行

3、運行狀態(Running):可運行狀態(runnable)的線程獲得了cpu 時間片(timeslice) ,執行程序代碼。 當CPU開始調度處於就緒狀態的線程時,此時線程才得以真正執行,即進入到運行狀態。注:就 緒狀態是進入到運行狀態的唯一入口,也就是說,線程要想進入運行狀態執行,首先必須處於就緒狀態中;

4、阻塞狀態(Blocked):處於運行狀態中的線程由於某種原因,暫時放棄對CPU的使用權,,也即讓出了cpu timeslice, 停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU調用以進入到運行狀態。纔有機會再次獲得cpu timeslice 轉到運行(running)狀態 根據阻塞產生的原因不同,阻塞狀態又可以分爲三種:

  1. 等待阻塞:運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態;JVM會把該線程放入等待隊列(waitting queue)中
  2. 同步阻塞:運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,獲取synchronized同步鎖失敗 , 它會進入同步阻塞狀態 ,則JVM會把該線程放入鎖池(lock pool)中
  3. 其他阻塞:通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入可運行(runnable)狀態

5、死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。


2、sleep、yield、join、wait總結

  • sleep:釋放cpu資源,不釋放鎖資源。Thread類的方法,必須帶一個時間參數。會讓當前線程休眠進入阻塞狀態並釋放CPU,提供其他線程運行的機會且不考慮優先級,但如果有同步鎖則sleep不會釋放鎖即其他線程無法獲得同步鎖  可通過調用interrupt()方法來喚醒休眠線程。
  • yield:讓出CPU調度,Thread類的方法,類似sleep只是不能由用戶指定暫停多長時間並且yield()方法只能讓同優先級的線程有執行的機會。 yield()只是使當前線程重新回到可執行狀態,所以執行yield()的線程有可能在進入到可執行狀態後馬上又被執行。調用yield方法只是一個建議,告訴線程調度器我的工作已經做的差不多了,可以讓別的相同優先級的線程使用CPU了,沒有任何機制保證採納。
  • wait:釋放cpu資源,也釋放鎖資源。Object類的方法(notify()、notifyAll()  也是Object對象),必須放在循環體和同步代碼塊中,執行該方法的線程會釋放鎖,進入線程等待池中等待被再次喚醒(notify隨機喚醒,notifyAll全部喚醒,線程結束自動喚醒)即放入鎖池中競爭同步鎖
  • join:Thread類方法,一種特殊的wait,當前運行線程調用另一個線程的join方法,則當前線程進入阻塞狀態直到另一個線程運行結束等待該線程終止。 注意該方法也需要捕捉異常。等待調用join方法的線程結束,再繼續執行。如:t.join();主要用於等待 t 線程 運行結束,若無此句,main則會執行完畢,導致結果不可預測。
join() 一直等待
join(long millis) 等待指定毫秒數
join(long millis, int nanos) 等待指定毫秒數

 


3、interrupt總結

背景:

  1. 在Java中沒有辦法立即停止一個線程(Thread.stop()方法已經被標記爲廢棄,因爲stop方法停止線程太過於粗暴,立即停止線程,不給釋放資源後續處理等操作留有餘地,會出現安全隱患)。
  2. 如果在一個線程中想控制另外一個線程的狀態,例如:取消一個耗時操作。這個時候就可以使用java的中斷機制

什麼是中斷:

  • 中斷只是一種協作機制,Java沒有給中斷增加任何語法,中斷的過程完全需要程序員自己實現。若要中斷一個線程,你需要手動調用該線程的interrupted方法,該方法也僅僅是將線程對象的中斷標識設成true;接着你需要自己寫代碼不斷地檢測當前線程的標識位;如果爲true,表示別的線程要求這條線程中斷,此時究竟該做什麼需要你自己寫代碼實現。
  • 每個線程對象中都有一個標識,用於表示線程是否被中斷;該標識位爲true表示中斷,爲false表示未中斷;
  • 通過調用線程對象的interrupt方法將該線程的標識位設爲true;可以在別的線程中調用,也可以在自己的線程中調用。

中斷的相關方法:

public void interrupt() 將調用者線程的中斷狀態設爲true。
public boolean isInterrupted() 判斷調用者線程的中斷狀態。
public static boolean interrupted 只能通過Thread.interrupted()調用。 它會做兩步操作:
返回當前線程的中斷狀態;將當前線程的中斷狀態設爲false;

中斷方法的使用:

/**
 * @Description 測試不同狀態下線程被中斷後的行爲
 * @Author lhy
 * @Date 2019/11/21 15:01
 */
public class TheadStatusInterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        testRunningInterrupt();
        testBlockedInterrupt();
    }

    /**
     * 測試阻塞狀態下被interrupt
     * @throws InterruptedException
     */
    private static void testBlockedInterrupt() throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted()) {
                    try {
                        Thread.currentThread().sleep(5000);
                    } catch (InterruptedException e) {
                        System.out.println("catch:" + Thread.currentThread().isInterrupted());
                        break;
                    }
                }
                //經過catch InterruptedException 處理後的線程中斷狀態已經被重置
                System.out.println("interrupted!!!:" + Thread.currentThread().isInterrupted());
            }
        });
        t1.start();
        Thread.currentThread().sleep(2000);
        t1.interrupt();
        t1.join();
    }

    /**
     * 測試運行中狀態的線程被interrupt
     * @throws InterruptedException
     */
    private static void testRunningInterrupt() throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println("running...");
                }
                //中斷處理的代碼
                System.out.println("interrupted!!!" + Thread.currentThread().isInterrupted());
            }
        });
        t1.start();
        Thread.currentThread().sleep(2000);
        t1.interrupt();
        t1.join();
    }
}

 

    /**
     * 測試運行中狀態的線程被interrupt
     * @throws InterruptedException
     */
    private static void testRunningAfterBlockedInterrupt() throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (true) {
                    System.out.println("running...:"+i++);
                    if(i>5000){
                        try {
                            System.out.println("sleep:" +     
                            Thread.currentThread().isInterrupted());
                            //阻塞之前,interrupt被設置爲true,此次會拋出異常
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            //catch中 interrupt狀態位已經被自動重置爲false
                            System.out.println("interrupted!!!" + 
                            Thread.currentThread().isInterrupted());
                            break;
                        }
                    }
                }
            }
        });
        t1.start();
        t1.interrupt();
        t1.join();
    }

總結:

  • interrupt方法簡單的把中斷狀態設置爲ture,被通知中斷的線程在阻塞時,這個時候拋出異常,並且中斷狀態爲false。
  • interrupted方法做兩件事,返回當前中斷狀態,和清除中斷狀態,不能恢復false爲true,只能把true清除爲false。
     

    要真正理解interrupt()方法,要先了解stop()方法。在以前通過thread.stop()可以停止一個線程,注意stop()方法是可以由一個線程去停止另外一個線程,這種方法太過暴力而且是不安全的,怎麼說呢,線程A調用線程B的stop方法去停止線程B,調用這個方法的時候線程A其實並不知道線程B執行的具體情況,這種突然間地停止會導致線程B的一些清理工作無法完成,還有一個情況是執行stop方法後線程B會馬上釋放鎖,這有可能會引發數據不同步問題。基於以上這些問題,stop()方法被拋棄了。
在這樣的情況下,interrupt()方法出現了,它與stop不同,它不會真正停止一個線程,它僅僅是給這個線程發了一個信號告訴它它應該結束了(設置一個停止標誌)。真正符合安全的做法,就是讓線程自己去結束自己,而不是讓一個線程去結束另外一個線程。

    通過interrupt()和.interrupted()方法兩者的配合可以實現正常去停止一個線程,線程A通過調用線程B的interrupt方法通知線程B讓它結束線程,在線程B的run方法內部,通過循環檢查.interrupted()方法是否爲真來接收線程A的信號,如果爲真就可以拋出一個異常,在catch中完成一些清理工作,然後結束線程。Thread.interrupted()會清除標誌位,並不是代表線程又恢復了,可以理解爲僅僅是代表它已經響應完了這個中斷信號然後又重新置爲可以再次接收信號的狀態。從始至終,理解一個關鍵點,interrupt()方法僅僅是改變一個標誌位的值而已,和線程的狀態並沒有必然的聯繫。

 

 

 

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