JDK版本:1.8java
閱讀了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;
// 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方法。this
注意:咱們永遠不要對同一個線程對象執行兩次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方法,能夠設置中斷標誌位,設置了中斷以後不必定有影響,還須要知足必定的條件才能發揮做用:3d
// 設置別的線程中斷 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方法和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方法會讓線程進入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); }
告訴操做系統的調度器:個人cpu能夠先讓給其餘線程,可是我佔有的同步資源不讓。
注意,調度器能夠不理會這個信息。這個方法幾乎沒用,調試併發bug可能能派上用場
public static native void yield();
有些場景是須要根據線程的優先級來調度的,優先級越大越先執行,最大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()
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) * */
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(); } /** * 輸出: * 你好 * 你也好 * 你更好! * 你最好!! */ }