Java線程生命周期的終止與復位

Thread生命周期

生命周期概述

Java的線程狀態描述放在Thread類裡面的枚舉類State中.總共包含瞭6中狀態(從出生到死亡)。

public enum State {
    /**
     * 尚未啟動的線程的線程狀態 (沒有start)
     */
    NEW,

    /**
     * 可運行線程的線程狀態,是可以運行的線程狀態(並不是在運行)
     * 這個狀態在Java虛擬機中進行,但它可能等待來自操作系統的其他資源,比如CPU。
     * 內部包含瞭兩個狀態 【RUNNING】,【READY】這兩個狀態是可以互相流轉的
     * 調用瞭start後線程就處於 READY 狀態 ,等待操作系統分配CPU時間片,分配後進入 RUNNING 狀態。
     * 當調用 yield() 方法後,隻是謙讓的允許當前線程讓出 CPU ,但是不一定讓,由操作系統決定,如果讓      * 瞭當前線程就會進入 READY 狀態,等待系統分配CPU時間片再次進入 RUNNING 狀態。
     */
    RUNNABLE,

    /**
     * 阻塞狀態。
     * 線程阻塞,等待監視器鎖的狀態,獲取監視器鎖後會進入 RUNNABLE  狀態
     * 當發生線程鎖競爭狀態下,沒有獲取到鎖的線程會被掛起進入阻塞狀態,比如synchronized鎖。
     */
    BLOCKED,

    /**
     * 等待線程的線程狀態
     * 線程調用以下方法會處於等待狀態:Object.wait()不超時、Thread.join()不超時等方法
     * 一個處於等待狀態的線程正在等待另一個線程執行特定動作,例如:
     * 一個線程調用瞭Object.wait()方法在一個對象上正在等待另一個線程調用Object.nofify()或者
     * Object.nofifyAll()方法開啟那個對象
     * 一個調用瞭Thread.join()方法的線程正在等待指定線程終止
     */
    WAITING,

    /**
     * 具有指定等待時間的等待線程的線程狀態,調用一下方法會處於這個狀態: Object.wait() 超時、          * Thread.join()超時 Thread.sleep(long) 等方法 
     */
    TIMED_WAITING,

    /**
     * 已終止線程的線程狀態
     * 線程執行完畢或者發生異常終止執行
     */
    TERMINATED;
}

線程生命周期流程圖

線程生命周期測試

public class ThreadStatusDemo {
    public static void main(String[] args) throws InterruptedException {
        // 測試 NEW RUNNABLE TERMINATED
        Thread terminated_thread = new Thread(() -> {
            long start = System.currentTimeMillis();
            // 運行三秒 ,打印TERMINATED_THREAD線程runnable狀態
            while (System.currentTimeMillis()-start<3000){}
        }, "TERMINATED_THREAD");
        // NEW
        Thread.State state = terminated_thread.getState();
        System.out.println(terminated_thread.getName()+" :state = " + state);

        terminated_thread.start();
        TimeUnit.SECONDS.sleep(1);
        // RUNNABLE
        Thread.State state1 = terminated_thread.getState();
        System.out.println(terminated_thread.getName()+"state1 = " + state1);

        TimeUnit.SECONDS.sleep(5);
        Thread.State state2 = terminated_thread.getState();
        // TERMINATED
        System.out.println(terminated_thread.getName()+"state2 = " + state2);

        // RUNNABLE
        new Thread(() -> {
            while (true) {

            }
        }, "Runnle_Thread").start();
        // TIMED_WAITING
        new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Time_Waiting_Thread").start();
        // WAITING
        new Thread(() -> {
            while (true) {
                synchronized (ThreadStatusDemo.class) {
                    try {
                        ThreadStatusDemo.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "Waiting_Thread").start();

        // 這兩個看誰先搶占到cpu獲得鎖,另一個就blocked
        // timed_waiting
        new Thread(new BlockedDemo(), "Blocke01_Thread").start();
        // blocked
        new Thread(new BlockedDemo(), "Blocke02_Thread").start();
    }
    static class BlockedDemo extends Thread {
        @Override
        public void run() {
            synchronized (BlockedDemo.class) {
                while (true) {
                    try {
                        TimeUnit.SECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

啟動線程

java中的啟動

Java啟動一個線程調用start方法,start方法內部調用瞭 start0()native方法。

public synchronized void start() {
    . . .
    boolean started = false;
    try { 
        // 調用native方法
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

這個測試是為瞭驗證上圖的正確性,隻貼瞭部分.

Hotspot中的啟動

查看指引:

 在jvm.cpp找到JVM_StartThread方法。發現是先創建個 JavaThread作為本地線程然後啟動這個本地線程(借助os【thread.cpp】,因為jvm是跨平臺的,這裡是以linux-os為示例)

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;
  bool throw_illegal_thread_state = false;
  {
    MutexLocker mu(Threads_lock);

    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true;
    } else {
      jlong size =
             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
      size_t sz = size > 0 ? (size_t) size : 0;
      // 先創建一個JavaThread
      native_thread = new JavaThread(&thread_entry, sz);
      if (native_thread->osthread() != NULL) {
        native_thread->prepare(jthread);
      }
    }
  }
  if (throw_illegal_thread_state) {
    THROW(vmSymbols::java_lang_IllegalThreadStateException());
  }
  assert(native_thread != NULL, "Starting null thread?");
  if (native_thread->osthread() == NULL) {
    delete native_thread;
    if (JvmtiExport::should_post_resource_exhausted()) {
      JvmtiExport::post_resource_exhausted(
        JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
        "unable to create new native thread");
    }
    THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
              "unable to create new native thread");
  }
  // 然後啟動這個本地線程 thread.cpp
  Thread::start(native_thread);
JVM_END

JavaThread 創建線程:

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
  Thread()
#if INCLUDE_ALL_GCS
  , _satb_mark_queue(&_satb_mark_queue_set),
  _dirty_card_queue(&_dirty_card_queue_set)
#endif // INCLUDE_ALL_GCS
{
  if (TraceThreadEvents) {
    tty->print_cr("creating thread %p", this);
  }
  initialize();
  _jni_attach_state = _not_attaching_via_jni;
  set_entry_point(entry_point);
  os::ThreadType thr_type = os::java_thread;
  thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
                                                     os::java_thread
// 調用os(操作系統)創建個線程
  os::create_thread(this, thr_type, stack_sz);
  _safepoint_visible = false;
   . . .
}

thread.cpp 啟動線程:

// tips: 啟動線程的方法
void Thread::start(Thread* thread) {
  trace("start", thread);
  // Start is different from resume in that its safety is guaranteed by context or
  // being called from a Java method synchronized on the Thread object.
  if (!DisableStartThread) {
    if (thread->is_Java_thread()) {
      // Initialize the thread state to RUNNABLE before starting this thread.
      // Can not set it after the thread started because we do not know the
      // exact thread state at that time. It could be in MONITOR_WAIT or
      // in SLEEPING or some other state.
      // tips:啟動之後設置線程的狀態為 可運行狀態 RUNNABLE
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
    // 借助操作系統啟動線程
    os::start_thread(thread);
  }
}

線程中斷與復位

不要使用stop方法

線程的終止不要簡單的調用 stop方法,這個方法和其他的線程控制方法(suspend,resume)一樣都是過期瞭不建議使用的,這些方法都是不安全的。 例如stop()方法在結束一個線程的時候並不保證線程資源的正常釋放,因此可能導致出現一些不確定的狀態。 按照人類邏輯來理解:T1線程調用方法修改T2線程的狀態,但是T2現在在做什麼T1是不清楚的,所以強制他關閉就是不安全的,就好比在Linux中使用 kill -9 殺掉一個進程。

使用interrupt方法

interrupt()方法隻是修改瞭被中斷線程的中斷標志 ,並沒有做什麼過分的事兒。就像平時寫代碼的時候修改某對象的標志,對象自己通過標志類決定執行什麼邏輯。這裡也是一樣,interrupt()方法修改中斷標志,被中斷的線程,自己決定做什麼事兒(中斷或者不中斷都是被中斷線程自己決定的,外部隻是通知他,不是強迫他)。追一下源碼。

1.Java調用interrupt方法 

2.通過指引找到 jvm.cpp#JVM_Interrupt方法

thread.cpp interrupt 借用操作系統。直接通過系統調用 interrupt
void Thread::interrupt(Thread* thread) {
  trace("interrupt", thread);
  debug_only(check_for_dangling_thread_pointer(thread);)
  // tips: 調用操作系統的interrupt方法
  os::interrupt(thread);
}

這裡還是以os_linux.cpp為例最終調用osthread的set_interrupted修改狀態 

這裡就印證瞭上方的 Thread.interrupt()隻是修改瞭線程的一個標志位 ,並沒有做什麼過分的事兒。

線程的復位

interruptedisInterrupted

這兩個放在一起是因為他們底層都是調用的同一個native方法isInterrupted()隻是給瞭不同的入參。 再就是,有過面試官問到他兩的區別,所以幹脆放在一起。首先說結論 ,isInterrupted()會返回線程的中斷狀態,interrupted()不僅會返回中斷狀態,而且如果線程處於狀態狀態還會將線程終端狀態復位(清除中斷狀態)。

os_linux.cpp的is_interrupted()方法印證瞭上面說的isInterrupted()會返回線程的中斷狀態,interrupted()不僅會返回中斷狀態,而且如果線程處於狀態狀態還會將線程終端狀態復位(清除中斷狀態)。

其他的線程復位

Java中隻要拋出瞭InnterruptException異常的方法都對線程進行瞭復位。先理順下為什麼要這麼做:查看下基本上拋出InnterruptException異常的方法都是線程阻塞方法,比如sleep(),wait(),join()。這類方法執行後線程會處於TIMED_WAITING或者WAITING狀態,處於這類狀態的線程是不受控的(線程喪失瞭對自己的主導,需要其他的線程喚醒,或者阻塞時間到達才能擁有自己的主導權),這個時候線程中斷,線程自己卻沒辦法處理。甚至可能永遠等不到釋放而無法執行中斷。所以,在線程是中斷狀態下,執行方法讓線程阻塞,就要拋出一個異常告訴外界 ,我現在是阻塞狀態,並且將中斷標記復位,方便外界進行處理(例如中斷線程的執行或者繼續阻塞方法),相當於給瞭外界一個改變線程狀態的入口。 以sleep()為例追蹤下源碼:

通過指引找到 jcm.cpp#JVM_Sleep

方法入口就直接判斷線程的中斷狀態瞭 ,is_interrupted()上面介紹過瞭,參數為true就是清除中斷標志並且返回清除之前的中斷狀態。這裡線程是中斷狀態的就直接拋出 InnterruptException sleep interrupted異常瞭。

到此這篇關於Java線程生命周期的終止與復位的文章就介紹到這瞭,更多相關Java線程生命周期內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: