1. 程式人生 > >Thread類原始碼剖析

Thread類原始碼剖析

目錄

1.引子

2.JVM執行緒狀態

3.Thread常用方法

4.拓展點

一、引子

說來也有些汗顏,搞了幾年java,忽然發現竟然沒拜讀過java.lang.Thread類原始碼,這次特地拿出來晒一晒。本文將剖析Thread類原始碼(本文後面原始碼全部預設JDK8),並講解一些重要的拓展點。希望對大家能有一些幫助。

本文講解主幹全部出自原始碼和註釋,保證了權威性。(注意:網上,某些書中很多觀點都是錯的,過時的,片面的,所以大家一定要看原始碼,重要事情說N遍,看原始碼!看原始碼!看原始碼......)

二、JVM執行緒狀態

在正式學習Thread類中的具體方法之前,我們先來了解一下執行緒有哪些狀態,這個將會有助於後面對Thread類中的方法的理解。

自JDK5開始,執行緒包括以下6個狀態,摘自Thread.State:

複製程式碼

 1     /**
 2      * A thread state.  A thread can be in one of the following states:
 3      * <ul>
 4      * <li>{@link #NEW}<br>
 5      *     A thread that has not yet started is in this state.
 6      *     </li>
 7      * <li>{@link #RUNNABLE}<br>
 8      *     A thread executing in the Java virtual machine is in this state.
 9      *     </li>
10      * <li>{@link #BLOCKED}<br>
11      *     A thread that is blocked waiting for a monitor lock
12      *     is in this state.
13      *     </li>
14      * <li>{@link #WAITING}<br>
15      *     A thread that is waiting indefinitely for another thread to
16      *     perform a particular action is in this state.
17      *     </li>
18      * <li>{@link #TIMED_WAITING}<br>
19      *     A thread that is waiting for another thread to perform an action
20      *     for up to a specified waiting time is in this state.
21      *     </li>
22      * <li>{@link #TERMINATED}<br>
23      *     A thread that has exited is in this state.
24      *     </li>
25      * </ul>
26      *
27      * <p>
28      * A thread can be in only one state at a given point in time.----》JVM中的執行緒必須只能是以上6種狀態的一種。這些狀態是JVM狀態並不能和作業系統執行緒狀態互相對映。
29      * These states are virtual machine states which do not reflect
30      * any operating system thread states.
31      *
32      * @since   1.5
33      * @see #getState
34      */
35     public enum State {
36         /**
37          * Thread state for a thread which has not yet started.
38          */
39         NEW,--->執行緒剛建立,還未執行(start方法)
40 
41         /**
42          * Thread state for a runnable thread.  A thread in the runnable
43          * state is executing in the Java virtual machine but it may
44          * be waiting for other resources from the operating system
45          * such as processor.
46          */
47         RUNNABLE,--->已就緒可執行的狀態。處於此狀態的執行緒是正在JVM中執行的,但可能在等待作業系統級別的資源,例如CPU時間片
48 
49         /**
50          * Thread state for a thread blocked waiting for a monitor lock.
51          * A thread in the blocked state is waiting for a monitor lock
52          * to enter a synchronized block/method or
53          * reenter a synchronized block/method after calling
54          * {@link Object#wait() Object.wait}.
55          */
56         BLOCKED,--->阻塞等待監視器鎖。處於此狀態的執行緒正在阻塞等待監視器鎖,以進入一個同步塊/方法,或者在執行完wait()方法後重入同步塊/方法。
57 
58         /**
59          * Thread state for a waiting thread.
60          * A thread is in the waiting state due to calling one of the
61          * following methods:
62          * <ul>
63          *   <li>{@link Object#wait() Object.wait} with no timeout</li>
64          *   <li>{@link #join() Thread.join} with no timeout</li>
65          *   <li>{@link LockSupport#park() LockSupport.park}</li>
66          * </ul>
67          *
68          * <p>A thread in the waiting state is waiting for another thread to
69          * perform a particular action.
70          *
71          * For example, a thread that has called <tt>Object.wait()</tt>
72          * on an object is waiting for another thread to call
73          * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
74          * that object. A thread that has called <tt>Thread.join()</tt>
75          * is waiting for a specified thread to terminate.
76          */
77         WAITING,--->等待。執行完Object.wait無超時引數操作,或者 Thread.join無超時引數操作(進入等待指定的執行緒執行結束),或者 LockSupport.park操作後,執行緒進入等待狀態。
                       一般在等待狀態的執行緒在等待其它執行緒執行特殊操作,例如:等待另其它執行緒操作Object.notify()喚醒或者Object.notifyAll()喚醒所有。
78 
79         /**
80          * Thread state for a waiting thread with a specified waiting time.
81          * A thread is in the timed waiting state due to calling one of
82          * the following methods with a specified positive waiting time:
83          * <ul>
84          *   <li>{@link #sleep Thread.sleep}</li>
85          *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
86          *   <li>{@link #join(long) Thread.join} with timeout</li>
87          *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
88          *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
89          * </ul>
90          */
91         TIMED_WAITING,--->限時等待。Thread.sleep、Object.wait帶超時時間、Thread.join帶超時時間、LockSupport.parkNanos、LockSupport.parkUntil這些操作會時執行緒進入限時等待。
92 
93         /**
94          * Thread state for a terminated thread.
95          * The thread has completed execution.
96          */
97         TERMINATED;--->終止,執行緒執行完畢。
98     }

複製程式碼

看了原始碼6種狀態,很多人會迷惑怎麼沒有Running狀態呢?好吧,請相信原始碼,不要混淆作業系統執行緒狀態和java執行緒狀態。JVM中的執行緒必須只能是以上6種狀態的一種!(見上圖列舉State 註釋中的紅色部分)。

Running其實是早期作業系統下“單執行緒程序”的狀態,如下圖:

 注意:上圖已年久失修,不可參考!!!!

好吧,現在是不是覺得三觀被顛覆...

最新JAVA(JVM)執行緒狀態轉換如下圖

 

如上圖,可見:RUNNABLE = 正在JVM中執行的(Running)+ 可能在等待作業系統級別的資源(Ready),例如CPU時間片

  執行緒建立之後,不會立即進入就緒狀態,因為執行緒的執行需要一些條件(比如記憶體資源),只有執行緒執行需要的所有條件滿足了,才進入就緒狀態。

  當執行緒進入就緒狀態後,不代表立刻就能獲取CPU執行時間,也許此時CPU正在執行其他的事情,因此它要等待。當得到CPU執行時間之後,執行緒便真正進入執行狀態。

  執行緒在執行狀態過程中,可能有多個原因導致當前執行緒不繼續執行下去,比如使用者主動讓執行緒睡眠(睡眠一定的時間之後再重新執行)、使用者主動讓執行緒等待,或者被同步塊給阻塞,此時就對應著多個狀態:time waiting(睡眠或等待一定的事件)、waiting(等待被喚醒)、blocked(阻塞)。

  當由於突然中斷或者子任務執行完畢,執行緒就會被消亡。

三.Thread類中的方法

老規矩,先看原始碼註釋:

  Thread類實現了Runnable介面,在Thread類中,

  關鍵屬性

  name是表示Thread的名字,可以通過Thread類的構造器中的引數來指定執行緒名字,

  priority表示執行緒的優先順序(最大值為10,最小值為1,預設值為5),

  daemon表示執行緒是否是守護執行緒,如果在main執行緒中建立了一個守護執行緒,當main方法執行完畢之後,守護執行緒也會隨著消亡。在JVM中,垃圾收集器執行緒就是守護執行緒。

  target表示要執行的任務。

  group執行緒群組

  關鍵方法

  以下是關係到執行緒執行狀態的幾個方法:

  1)start

  start()用來啟動一個執行緒,當呼叫start方法後,系統才會開啟一個新的執行緒來執行使用者定義的子任務,在這個過程中,會為相應的執行緒分配需要的資源。

  2)run

  run()方法是不需要使用者來呼叫的,當通過start方法啟動一個執行緒之後,當執行緒獲得了CPU執行時間,便進入run方法體去執行具體的任務。注意,繼承Thread類必須重寫run方法,在run方法中定義具體要執行的任務。

  3)sleep

  sleep方法有兩個過載版本:

1 public static native void sleep(long millis) throws InterruptedException;
2 
3 public static void sleep(long millis, int nanos) throws InterruptedException; 

  sleep讓執行緒睡眠,交出CPU,讓CPU去執行其他的任務。sleep方法不會釋放鎖,也就是說如果當前執行緒持有對某個物件的鎖,則即使呼叫sleep方法,其他執行緒也無法訪問這個物件。sleep方法相當於讓執行緒進入阻塞狀態。

  4)yield

  呼叫yield方法會讓當前執行緒交出CPU許可權,讓CPU去執行其他的執行緒。它跟sleep方法類似,同樣不會釋放鎖。但是yield不能控制具體的交出CPU的時間,另外,yield方法只能讓擁有相同優先順序的執行緒有獲取CPU執行時間的機會。

  注意,呼叫yield方法並不會讓執行緒進入阻塞狀態,而是讓執行緒重回就緒狀態,它只需要等待重新獲取CPU執行時間,這一點是和sleep方法不一樣的。

  5)join

  join方法有三個過載版本:

1 join()
2 join(long millis)     //引數為毫秒
3 join(long millis,int nanoseconds)    //第一引數為毫秒,第二個引數為納秒

  可以看出,當呼叫thread.join()方法後,main執行緒會進入等待,然後等待thread執行完之後再繼續執行。

  實際上呼叫join方法是呼叫了Object的wait方法,這個可以通過檢視原始碼得知:

  

  wait方法會讓執行緒進入阻塞狀態,並且會釋放執行緒佔有的鎖,並交出CPU執行許可權。

  6)interrupt

  interrupt,中斷。單獨呼叫interrupt方法可以使得處於阻塞狀態的執行緒丟擲一個異常,也就說,它可以用來中斷一個正處於阻塞狀態的執行緒;

  7)stop

  stop方法已經是一個廢棄的方法,它是一個不安全的方法。因為呼叫stop方法會直接終止run方法的呼叫,並且會丟擲一個ThreadDeath錯誤,如果執行緒持有某個物件鎖的話,會完全釋放鎖,導致物件狀態不一致。所以stop方法基本是不會被用到的。

  8)destroy

  destroy方法也是廢棄的方法。基本不會被使用到。

四、拓展點

1.LookSupport.park()和unpark()原理

LockSupport類是Java6(JSR166-JUC)引入的一個類,提供了基本的執行緒同步原語。LockSupport實際上是呼叫了Unsafe類裡的函式,歸結到Unsafe裡,只有兩個函式:

掛起
public native void park(boolean isAbsolute, long time);

喚醒

public native void unpark(Thread jthread); 

unpark函式為執行緒提供“許可(permit)”,park函式則等待“許可”。這個有點像訊號量,但是這個“許可”是不能疊加的,“許可”是一次性的。

比如執行緒B連續呼叫了三次unpark函式,當執行緒A呼叫park函式就使用掉這個“許可”,如果執行緒A再次呼叫park,則進入等待狀態。

注意,unpark函式可以先於park呼叫。比如執行緒B呼叫unpark函式,給執行緒A發了一個“許可”,那麼當執行緒A呼叫park時,它發現已經有“許可”了,那麼它會馬上再繼續執行。

實際上,park函式即使沒有“許可”,有時也會無理由地返回,這點等下再解析。

park/unpark模型真正解耦了執行緒之間的同步,執行緒之間不再需要一個Object或者其它變數來儲存狀態,不再需要關心對方的狀態。

我們從JDK原始碼開始看,java.util.concurrent.locks.LookSupport.park()如下:

複製程式碼

 1 /**
 2      * Disables the current thread for thread scheduling purposes unless the
 3      * permit is available.--->停止當前執行緒的排程執行一直到許可可達。
 4      *
 5      * <p>If the permit is available then it is consumed and the call
 6      * returns immediately; otherwise the current thread becomes disabled
 7      * for thread scheduling purposes and lies dormant until one of three
 8      * things happens:
 9      *--->當許可條件滿足時,當前執行緒會立即返回。否則會一直停止執行緒排程並且假死一直到下面3件事情發生:
10      * <ul>
11      *
12      * <li>Some other thread invokes {@link #unpark unpark} with the
13      * current thread as the target; or
14      *--->1.其它執行緒呼叫unpark方法喚醒此執行緒
15      * <li>Some other thread {@linkplain Thread#interrupt interrupts}
16      * the current thread; or
17      *--->2.其它執行緒中斷此執行緒
18      * <li>The call spuriously (that is, for no reason) returns.
19      * </ul>
20      **--->3.此執行緒未知錯誤返回了
21      * <p>This method does <em>not</em> report which of these caused the
22      * method to return. Callers should re-check the conditions which caused
23      * the thread to park in the first place. Callers may also determine,
24      * for example, the interrupt status of the thread upon return.
        *----》該方法不會告知是哪個原因導致的返回。呼叫方需要重新校驗導致執行緒park的條件。比如中斷狀態。
25      */
26     public static void park() {
27         UNSAFE.park(false, 0L);//執行緒呼叫該方法,執行緒將一直阻塞直到超時(這裡沒有超時時間為0),或者是中斷條件出現。 
28     }

複製程式碼

這裡我們就簡單看一下park()原始碼,目錄:
openjdk-8-src-b132-03_mar_2014\openjdk\hotspot\src\share\vm\runtime\park.cpp
openjdk-8-src-b132-03_mar_2014\openjdk\hotspot\src\share\vm\runtime\park.hpp
openjdk-8-src-b132-03_mar_2014\openjdk\hotspot\src\os\linux\vm\os_linux.cpp
openjdk-8-src-b132-03_mar_2014\openjdk\hotspot\src\os\linux\vm\os_linux.hpp

park.hpp:

複製程式碼

 1 class Parker : public os::PlatformParker {
 2 private:
 3   volatile int _counter ;
 4   Parker * FreeNext ;
 5   JavaThread * AssociatedWith ; // Current association
 6 
 7 public:
 8   Parker() : PlatformParker() {
 9     _counter       = 0 ;
10     FreeNext       = NULL ;
11     AssociatedWith = NULL ;
12   }
13 protected:
14   ~Parker() { ShouldNotReachHere(); }
15 public:
16   // For simplicity of interface with Java, all forms of park (indefinite,
17   // relative, and absolute) are multiplexed into one call.
18   void park(bool isAbsolute, jlong time);
19   void unpark();
20 
21   // Lifecycle operators
22   static Parker * Allocate (JavaThread * t) ;
23   static void Release (Parker * e) ;
24 private:
25   static Parker * volatile FreeList ;
26   static volatile int ListLock ;
27 
28 };

複製程式碼

os_linux.hpp中,PlatformParker:

複製程式碼

 1 class PlatformParker : public CHeapObj<mtInternal> {
 2   protected:
 3     enum {
 4         REL_INDEX = 0,
 5         ABS_INDEX = 1
 6     };
 7     int _cur_index;  // which cond is in use: -1, 0, 1
 8     pthread_mutex_t _mutex [1] ;
 9     pthread_cond_t  _cond  [2] ; // one for relative times and one for abs.
10 
11   public:       // TODO-FIXME: make dtor private
12     ~PlatformParker() { guarantee (0, "invariant") ; }
13 
14   public:
15     PlatformParker() {
16       int status;
17       status = pthread_cond_init (&_cond[REL_INDEX], os::Linux::condAttr());
18       assert_status(status == 0, status, "cond_init rel");
19       status = pthread_cond_init (&_cond[ABS_INDEX], NULL);
20       assert_status(status == 0, status, "cond_init abs");
21       status = pthread_mutex_init (_mutex, NULL);
22       assert_status(status == 0, status, "mutex_init");
23       _cur_index = -1; // mark as unused 初始化時-1未使用
24     }
25 };

複製程式碼

可以看到Parker類實際上用Posix的mutex,condition來實現的。
在Parker類裡的_counter欄位,就是用來記錄所謂的“許可”的。

park()原始碼實現,為了保證原始碼的完整性,就直接在原始碼上註釋原理了。

複製程式碼

  1 void Parker::park(bool isAbsolute, jlong time) {
  2   // Ideally we'd do something useful while spinning, such
  3   // as calling unpackTime().
  4 
  5   // Optional fast-path check:
  6   // Return immediately if a permit is available.
  7   // We depend on Atomic::xchg() having full barrier semantics
  8   // since we are doing a lock-free update to _counter.
  9   if (Atomic::xchg(0, &_counter) > 0) return;//先嚐試能否直接拿到“許可”,即_counter>0時,如果成功,則把_counter設定為0,並返回:
 10 
 11   Thread* thread = Thread::current();
 12   assert(thread->is_Java_thread(), "Must be JavaThread");
 13   JavaThread *jt = (JavaThread *)thread;
 14 
 15   // Optional optimization -- avoid state transitions if there's an interrupt pending.
 16   // Check interrupt before trying to wait
 17   if (Thread::is_interrupted(thread, false)) {
 18     return;
 19   }
 20 
 21   // Next, demultiplex/decode time arguments
 22   timespec absTime;
 23   if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all
 24     return;
 25   }
 26   if (time > 0) {
 27     unpackTime(&absTime, isAbsolute, time);
 28   }
 29 
 30 
 31   // Enter safepoint region
 32   // Beware of deadlocks such as 6317397.
 33   // The per-thread Parker:: mutex is a classic leaf-lock.
 34   // In particular a thread must never block on the Threads_lock while
 35   // holding the Parker:: mutex.  If safepoints are pending both the
 36   // the ThreadBlockInVM() CTOR and DTOR may grab Threads_lock.
 37   ThreadBlockInVM tbivm(jt);//如果不成功,則構造一個ThreadBlockInVM,
 38 
 39   // Don't wait if cannot get lock since interference arises from
 40   // unblocking.  Also. check interrupt before trying wait
 41   if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
 42     return;
 43   }
 44 
 45   int status ;
 46   if (_counter > 0)  { // no wait needed然後檢查_counter是不是>0,如果是,則把_counter設定為0,unlock mutex並返回:
 47     _counter = 0;
 48     status = pthread_mutex_unlock(_mutex);
 49     assert (status == 0, "invariant") ;
 50     // Paranoia to ensure our locked and lock-free paths interact
 51     // correctly with each other and Java-level accesses.
 52     OrderAccess::fence();
 53     return;
 54   }
 55 
 56 #ifdef ASSERT
 57   // Don't catch signals while blocked; let the running threads have the signals.
 58   // (This allows a debugger to break into the running thread.)
 59   sigset_t oldsigs;
 60   sigset_t* allowdebug_blocked = os::Linux::allowdebug_blocked_signals();
 61   pthread_sigmask(SIG_BLOCK, allowdebug_blocked, &oldsigs);
 62 #endif
 63 
 64   OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
 65   jt->set_suspend_equivalent();
 66   // cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()
 67 
 68   assert(_cur_index == -1, "invariant");
 69   if (time == 0) {
 70     _cur_index = REL_INDEX; // arbitrary choice when not timed
 71     status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
 72   } else {
 73     _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
 74     status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;
 75     if (status != 0 && WorkAroundNPTLTimedWaitHang) {
 76       pthread_cond_destroy (&_cond[_cur_index]) ;
 77       pthread_cond_init    (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr());
 78     }
 79   }
 80   _cur_index = -1;
 81   assert_status(status == 0 || status == EINTR ||
 82                 status == ETIME || status == ETIMEDOUT,
 83                 status, "cond_timedwait");
 84 
 85 #ifdef ASSERT
 86   pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
 87 #endif
 88 
 89   _counter = 0 ;
 90   status = pthread_mutex_unlock(_mutex) ;
 91   assert_status(status == 0, status, "invariant") ;
 92   // Paranoia to ensure our locked and lock-free paths interact
 93   // correctly with each other and Java-level accesses.
 94   OrderAccess::fence();
 95 
 96   // If externally suspended while waiting, re-suspend
 97   if (jt->handle_special_suspend_equivalent_condition()) {
 98     jt->java_suspend_self();
 99   }
100 }

複製程式碼

unpark()原始碼實現

複製程式碼

 1 void Parker::unpark() {
 2   int s, status ;
 3   status = pthread_mutex_lock(_mutex);//互斥鎖加鎖
 4   assert (status == 0, "invariant") ;
 5   s = _counter;//儲存初始counter
 6   _counter = 1;//置1
 7   if (s < 1) {//如果原本為0
 8     // thread might be parked執行緒可能被掛起
 9     if (_cur_index != -1) {
10       // thread is definitely parked
11       if (WorkAroundNPTLTimedWaitHang) {
12         status = pthread_cond_signal (&_cond[_cur_index]);//喚醒在park中等待的執行緒
13         assert (status == 0, "invariant");
14         status = pthread_mutex_unlock(_mutex);//釋放鎖
15         assert (status == 0, "invariant");
16       } else {
17         status = pthread_mutex_unlock(_mutex);//釋放鎖
18         assert (status == 0, "invariant");
19         status = pthread_cond_signal (&_cond[_cur_index]);//喚醒在park中等待的執行緒
20         assert (status == 0, "invariant");
21       }
22     } else {
23       pthread_mutex_unlock(_mutex);//釋放鎖
24       assert (status == 0, "invariant") ;
25     }
26   } else {//如果原本為1,釋放鎖
27     pthread_mutex_unlock(_mutex);
28     assert (status == 0, "invariant") ;
29   }
30 }

複製程式碼

2.Caches快取類

Caches-->WeakClassKey-->WeakReference

複製程式碼

 1 /** cache of subclass security audit results */
 2     /* Replace with ConcurrentReferenceHashMap when/if it appears in a future
 3      * release */
 4     private static class Caches {
 5         /** cache of subclass security audit results */
 6         static final ConcurrentMap<WeakClassKey,Boolean> subclassAudits =
 7             new ConcurrentHashMap<>();
 8 
 9         /** queue for WeakReferences to audited subclasses */
10         static final ReferenceQueue<Class<?>> subclassAuditsQueue =
11             new ReferenceQueue<>();
12     }

複製程式碼

 Caches類中包含了兩個成員subclassAudits和subclasseAuditsQueue:
  subclassAudits——該成員屬性提供了一個雜湊表快取,該快取的鍵型別為java.lang.Thread.WeakClassKey,注意看它的值型別是一個java.lang.Boolean型別的,從其程式碼註釋可以知道這個雜湊表快取中儲存的是所有子類的程式碼執行安全性檢測結果;
  subclassAuditsQueue——該成員屬性定義了一個“Queue佇列”,儲存了已經稽核過的子類弱引用

static class WeakClassKey extends WeakReference<Class<?>>關於弱引用WeakReference,飛機票:Java中關於WeakReference和WeakHashMap的理解

參考

《JAVA高併發程式設計》電子工業出版社