Java讀原始碼之Thread
前言
JDK版本:1.8
閱讀了Object的原始碼,wait和notify方法與執行緒聯絡緊密,而且多執行緒已經是必備知識,那保持習慣,就從多執行緒的源頭Thread類開始讀起吧。由於該類比較長,只讀重要部分
原始碼
類宣告和重要屬性
package java.lang; public class Thread implements Runnable { private volatile String name; // 優先順序 private int priority; //是否後臺 private boolean daemon = false; /* JVM state */ private boolean stillborn = false; // 要跑的任務 private Runnable target; // 執行緒組 private ThreadGroup group; // 上下文載入器 private ClassLoader contextClassLoader; // 許可權控制上下文 private AccessControlContext inheritedAccessControlContext; // 執行緒預設名字“Thread-{{ threadInitNumber }}” private static int threadInitNumber; // 區域性變數,每個執行緒擁有各自獨立的副本 ThreadLocal.ThreadLocalMap threadLocals = null; // 有時候區域性變數需要被子執行緒繼承 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; // 執行緒初始化時申請的JVM棧大小 private long stackSize; // 執行緒ID private long tid; // 執行緒init之後的ID private static long threadSeqNumber; // 0就是執行緒還處於NEW狀態,沒start private volatile int threadStatus = 0; // 給LockSupport.park用的需要競爭的物件 volatile Object parkBlocker; // 給中斷用的需要競爭的物件 private volatile Interruptible blocker; // 執行緒最小優先順序 public final static int MIN_PRIORITY = 1; // 執行緒預設優先順序 public final static int NORM_PRIORITY = 5; // 執行緒最大優先順序 public final static int MAX_PRIORITY = 10;
Java執行緒有幾種狀態?
// Thread類中的列舉
public enum State {
// 執行緒剛創建出來還沒start
NEW,
// 執行緒在JVM中運行了,需要去競爭資源,例如CPU
RUNNABLE,
// 執行緒等待獲取物件監視器鎖,損被別人拿著就阻塞
BLOCKED,
// 執行緒進入等待池了,等待覺醒
WAITING,
// 指定了超時時間
TIMED_WAITING,
// 執行緒終止
TERMINATED;
}
下面這個圖可以幫助理解Java執行緒的生命週期,這個圖要會畫!面試中被問到,當時畫的很不專業,難受!
建立
那麼執行緒如何進入初始New狀態呢?讓我們來看看構造,頭皮發麻,怎麼有七八個構造,這裡只貼了一個
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
還好都是呼叫init()方法,怕怕的點開了
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name; // 獲取當前執行緒,也就是需要被建立執行緒的爸爸 Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { // 通過security獲取執行緒組,其實就是拿的當前執行緒的組 if (security != null) { g = security.getThreadGroup(); } // 獲取當前執行緒的組,這下確保肯定有執行緒組了 if (g == null) { g = parent.getThreadGroup(); } } // check一下組是否存在和是否有執行緒組修改許可權 g.checkAccess(); // 子類執行許可權檢查,子類不能重寫一些不是final的敏感方法 if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } // 組裡未啟動的執行緒數加1,長時間不啟動就會被回收 g.addUnstarted(); // 執行緒的組,是否後臺,優先順序,初始全和當前執行緒一樣 this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) // 子類重寫check沒過或者就沒有security,這裡要check下是不是連裝載的許可權都沒有 this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; // 訪問控制上下文初始化 this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); // 任務初始化 this.target = target; // 設定許可權 setPriority(priority); // 如果有需要繼承的ThreadLocal區域性變數就copy一下 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); // 初始化JVM中待建立執行緒的棧大小 this.stackSize = stackSize; // threadSeqNumber執行緒號加1 tid = nextThreadID(); }
執行
現線上程已經是NEW狀態了,我們還需要呼叫start方法,讓執行緒進入RUNNABLE狀態,真正在JVM中快樂的跑起來,當獲得了執行任務所需要的資源後,JVM便會呼叫target(Runnable)的run方法。
注意:我們永遠不要對同一個執行緒物件執行兩次start方法
public synchronized void start() {
// 0就是NEW狀態
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 把當前執行緒加到執行緒組的執行緒陣列中,然後nthreads執行緒數加1,nUnstartedThreads沒起的執行緒數減1
group.add(this);
boolean started = false;
// 請求資源
try {
start0();
started = true;
} finally {
try {
if (!started) {
// 起失敗啦,把當前執行緒從執行緒組的執行緒陣列中刪除,然後nthreads減1,nUnstartedThreads加1
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
// start0出問題會自己列印堆疊資訊
}
}
}
private native void start0();
終止
現在我們的執行緒已經到RUNNABLE狀態了,一切順利的話任務執行完成,自動進入TERMINATED狀態,天有不測風雲,我們還會再各個狀態因為異常到達TERMINATED狀態。
Thread類為我們提供了interrupt方法,可以設定中斷標誌位,設定了中斷之後不一定有影響,還需要滿足一定的條件才能發揮作用:
- RUNNABLE狀態下
- 預設什麼都不會發生,需要程式碼中迴圈檢查 中斷標誌位
- WAITING/TIMED_WAITING狀態下
- 這兩個狀態下,會從物件等待池中出來,等拿到監視器鎖會丟擲InterruptedException異常,然後中斷標誌位被清空。
- BLOCKED狀態下
- 如果執行緒在等待鎖,對執行緒物件呼叫interrupt()只是會設定執行緒的中斷標誌位,執行緒依然會處於BLOCKED狀態
- NEW/TERMINATE狀態下
- 啥也不發生
// 設定別的執行緒中斷
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
// 拿一個可中斷物件Interruptible的鎖
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // 設定中斷標誌位
b.interrupt(this);
return;
}
}
interrupt0();
}
// 獲取當前執行緒中斷標誌位,然後重置中斷標誌位
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
// 檢查執行緒中斷標誌位
public boolean isInterrupted() {
return isInterrupted(false);
}
等待
主線已經做完了,下面來看下支線任務,同樣重要哦。從執行緒狀態圖看到,RUNNABLE狀態可以變成BLOCKED,WAITING或TIMED_WAITING。
其中BLOCKED主要是同步方法競爭鎖等同步資源造成的,而TIMED_WAITING主要是加了超時時間,其他和WAITING的內容差不多,唯一多了一個sleep方法。
sleep
果不其然,sleep方法和Object.wait方法如出一轍,都是呼叫本地方法,提供毫秒和納秒兩種級別的控制,唯一區別就是,sleep不會放棄任何佔用的監視器鎖
public static native void sleep(long millis) throws InterruptedException;
// 納秒級別控制
public static void sleep(long millis, int nanos) throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
join
join方法會讓執行緒進入WAITING,等待另一個執行緒的終止,整個方法和Object.wait方法也是很像,而且實現中也用到了wait
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
// 判斷呼叫join的執行緒是否活著,這裡的活著是指RUNNABLE,BLOCKED,WAITING,TIMED_WAITING這四種狀態,如果活著就一直等著,wait(0)意味著無限等
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
// 納秒級別控制
public final synchronized void join(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
join(millis);
}
public final void join() throws InterruptedException {
join(0);
}
其他方法
yield
告訴作業系統的排程器:我的cpu可以先讓給其他執行緒,但是我佔有的同步資源不讓。
注意,排程器可以不理會這個資訊。這個方法幾乎沒用,除錯併發bug可能能派上用場
public static native void yield();
setPriority
有些場景是需要根據執行緒的優先順序來排程的,優先順序越大越先執行,最大10,預設5,最小1
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
// 如果設定的優先順序,比執行緒所屬執行緒組中優先順序的最大值還大,我們需要更新最大值
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
// 本地方法
setPriority0(priority = newPriority);
}
}
棄用方法
有些熟悉的方法已經被棄用了,我們要避免使用
@Deprecated
public final void stop()
@Deprecated
public final synchronized void stop(Throwable obj)
@Deprecated
public void destroy()
@Deprecated
public final void suspend()
@Deprecated
public final void resume()
@Deprecated
public native int countStackFrames()
實踐
interrupt()
public class ThreadInterruptTest {
/**
* 如果我們同時呼叫了notify和interrupt方法,程式有可能正常執行結束,有可能丟擲異常結束,
* 原因是不管是因為notify還是interrupt,執行緒離開了等待池,都需要去競爭鎖,
* 如果interrupt呼叫瞬間拿到鎖,notify還沒有呼叫,就拋中斷異常
* 如果是interrupt呼叫瞬間拿不到鎖,此時中斷標誌位被重置,然後notify把執行緒拉到正常軌道,就繼續執行不拋中斷異常
*/
private static void testInterrupt() {
Object object = new Object();
Thread thread1 = new Thread(() -> {
synchronized (object) {
try {
object.wait();
System.out.println("我還活著!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
});
thread1.start();
new Thread(() -> {
// 只為了演示,實際很少用到這些方法,而且我們在執行中斷的同步程式碼塊中最好不要做別的事情,例如這裡的notify
synchronized (object) {
thread1.interrupt();
object.notify();
}
}).start();
}
public static void main(String[] args) {
for (int i = 0; i <5 ; i++) {
ThreadInterruptTest.testInterrupt();
}
}
}
/**
* 輸出:
* 我還活著!
* java.lang.InterruptedException
* at java.lang.Object.wait(Native Method)
* at java.lang.Object.wait(Object.java:502)
* at study.ThreadInterruptTest.lambda$testInterrupt$0(ThreadInterruptTest.java:15)
* at java.lang.Thread.run(Thread.java:748)
* java.lang.InterruptedException
* at java.lang.Object.wait(Native Method)
* at java.lang.Object.wait(Object.java:502)
* at study.ThreadInterruptTest.lambda$testInterrupt$0(ThreadInterruptTest.java:15)
* at java.lang.Thread.run(Thread.java:748)
* 我還活著!
* java.lang.InterruptedException
* at java.lang.Object.wait(Native Method)
* at java.lang.Object.wait(Object.java:502)
* at study.ThreadInterruptTest.lambda$testInterrupt$0(ThreadInterruptTest.java:15)
* at java.lang.Thread.run(Thread.java:748)
*
*/
join()
public class ThreadJoinTest {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println("你好");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("你更好!");
});
thread1.start();
new Thread(() -> {
System.out.println("你也好");
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("你最好!!");
}).start();
}
/**
* 輸出:
* 你好
* 你也好
* 你更好!
* 你最好!!
*/
}