前言
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,既然用到了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();
}
/**
* 輸出:
* 你好
* 你也好
* 你更好!
* 你最好!!
*/
}