Java
中的線程是使用Thread
類實現的,Thread
在初學Java
的時候就學過了,也在實踐中用過,不過一直沒從源碼的角度去看過它的實現,今天從源碼的角度出發,再次學習Java Thread
,願此後對Thread
的實踐更加駕輕就熟。java
相信閱讀過JDK
源碼的同窗都能感覺到JDK
源碼中有很是詳盡的註釋,閱讀某個類的源碼應當先看看註釋對它的介紹,註釋原文就不貼了,如下是我對它的總結:程序員
Thread
是程序中執行的線程,Java
虛擬機容許應用程序同時容許多個執行線程安全
每一個線程都有優先級的概念,具備較高優先級的線程優先於優先級較低的線程執行app
每一個線程均可以被設置爲守護線程less
當在某個線程中運行的代碼建立一個新的Thread
對象時,新的線程優先級跟建立線程一致ide
當Java
虛擬機啓動的時候都會啓動一個叫作main
的線程,它沒有守護線程,main
線程會繼續執行,直到如下狀況發送函數
Runtime
類的退出方法exit
被調用而且安全管理器容許進行退出操做run
方法執行結束正常返回結果,或者run
方法拋出異常建立線程第一種方式:繼承Thread
類,重寫run
方法學習
1//定義線程類
2class PrimeThread extends Thread {
3 long minPrime;
4 PrimeThread(long minPrime) {
5 this.minPrime = minPrime;
6 }
7 public void run() {
8 // compute primes larger than minPrime
9 . . .
10 }
11 }
12//啓動線程
13PrimeThread p = new PrimeThread(143);
14p.start();
建立線程第二種方式:實現Runnable
接口,重寫run
方法,由於Java
的單繼承限制,一般使用這種方式建立線程更加靈活測試
1//定義線程
2 class PrimeRun implements Runnable {
3 long minPrime;
4 PrimeRun(long minPrime) {
5 this.minPrime = minPrime;
6 }
7 public void run() {
8 // compute primes larger than minPrime
9 . . .
10 }
11 }
12//啓動線程
13PrimeRun p = new PrimeRun(143);
14new Thread(p).start();
建立線程時能夠給線程指定名字,若是沒有指定,會自動爲它生成名字this
除非另有說明,不然將null
參數傳遞給Thread
類中的構造函數或方法將致使拋出 NullPointerException
閱讀一個Java
類,先從它擁有哪些屬性入手:
1//線程名稱,建立線程時能夠指定線程的名稱
2private volatile String name;
3
4//線程優先級,能夠設置線程的優先級
5private int priority;
6
7//能夠配置線程是否爲守護線程,默認爲false
8private boolean daemon = false;
9
10//最終執行線程任務的`Runnable`
11private Runnable target;
12
13//描述線程組的類
14private ThreadGroup group;
15
16//此線程的上下文ClassLoader
17private ClassLoader contextClassLoader;
18
19//全部初始化線程的數目,用於自動編號匿名線程,當沒有指定線程名稱時,會自動爲其編號
20private static int threadInitNumber;
21
22//此線程請求的堆棧大小,若是建立者沒有指定堆棧大小,則爲0。, 虛擬機能夠用這個數字作任何喜歡的事情。, 一些虛擬機會忽略它。
23private long stackSize;
24
25//線程id
26private long tid;
27
28//用於生成線程ID
29private static long threadSeqNumber;
30
31//線程狀態
32private volatile int threadStatus = 0;
33
34//線程能夠擁有的最低優先級
35public final static int MIN_PRIORITY = 1;
36
37//分配給線程的默認優先級。
38public final static int NORM_PRIORITY = 5;
39
40//線程能夠擁有的最大優先級
41public final static int MAX_PRIORITY = 10;
全部的屬性命名都很語義化,其實已看名稱基本就猜到它是幹嗎的了,難度不大~~
瞭解了屬性以後,看看Thread
實例是怎麼構造的?先預覽下它大體有多少個構造方法:
查看每一個構造方法內部源碼,發現均調用的是名爲init
的私有方法,再看init
方法有兩個重載,而其核心方法以下:
1 /**
2 * Initializes a Thread.
3 *
4 * @param g 線程組
5 * @param target 最終執行任務的 `run()` 方法的對象
6 * @param name 新線程的名稱
7 * @param stackSize 新線程所需的堆棧大小,或者 0 表示要忽略此參數
8 * @param acc 要繼承的AccessControlContext,若是爲null,則爲 AccessController.getContext()
9 * @param inheritThreadLocals 若是爲 true,從構造線程繼承可繼承的線程局部的初始值
10 */
11 private void init(ThreadGroup g, Runnable target, String name,
12 long stackSize, AccessControlContext acc,
13 boolean inheritThreadLocals) {
14 //線程名稱爲空,直接拋出空指針異常
15 if (name == null) {
16 throw new NullPointerException("name cannot be null");
17 }
18 //初始化當前線程對象的線程名稱
19 this.name = name;
20 //獲取當前正在執行的線程爲父線程
21 Thread parent = currentThread();
22 //獲取系統安全管理器
23 SecurityManager security = System.getSecurityManager();
24 //若是線程組爲空
25 if (g == null) {
26 //若是安全管理器不爲空
27 if (security != null) {
28 //獲取SecurityManager中的線程組
29 g = security.getThreadGroup();
30 }
31 //若是獲取的線程組仍是爲空
32 if (g == null) {
33 //則使用父線程的線程組
34 g = parent.getThreadGroup();
35 }
36 }
37
38 //檢查安全權限
39 g.checkAccess();
40
41 //使用安全管理器檢查是否有權限
42 if (security != null) {
43 if (isCCLOverridden(getClass())) {
44 security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
45 }
46 }
47
48 //線程組中標記未啓動的線程數+1,這裏方法是同步的,防止出現線程安全問題
49 g.addUnstarted();
50
51 //初始化當前線程對象的線程組
52 this.group = g;
53 //初始化當前線程對象的是否守護線程屬性,注意到這裏初始化時跟父線程一致
54 this.daemon = parent.isDaemon();
55 //初始化當前線程對象的線程優先級屬性,注意到這裏初始化時跟父線程一致
56 this.priority = parent.getPriority();
57 //這裏初始化類加載器
58 if (security == null || isCCLOverridden(parent.getClass()))
59 this.contextClassLoader = parent.getContextClassLoader();
60 else
61 this.contextClassLoader = parent.contextClassLoader;
62 this.inheritedAccessControlContext =
63 acc != null ? acc : AccessController.getContext();
64 //初始化當前線程對象的最終執行任務對象
65 this.target = target;
66 //這裏再對線程的優先級字段進行處理
67 setPriority(priority);
68 if (inheritThreadLocals && parent.inheritableThreadLocals != null)
69 this.inheritableThreadLocals =
70 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
71 //初始化當前線程對象的堆棧大小
72 this.stackSize = stackSize;
73
74 //初始化當前線程對象的線程ID,該方法是同步的,內部其實是threadSeqNumber++
75 tid = nextThreadID();
76 }
另外一個重載init
私有方法以下,實際上內部調用的是上述init
方法:
1private void init(ThreadGroup g, Runnable target, String name,
2 long stackSize) {
3 init(g, target, name, stackSize, null, true);
4 }
接下來看看全部構造方法:
空構造方法
1 public Thread() {
2 init(null, null, "Thread-" + nextThreadNum(), 0);
3 }
內部調用的是init
第二個重載方法,參數基本都是默認值,線程名稱寫死爲"Thread-" + nextThreadNum()
格式,nextThreadNum()
爲一個同步方法,內部維護一個靜態屬性表示線程的初始化數量+1:
1 private static int threadInitNumber;
2 private static synchronized int nextThreadNum() {
3 return threadInitNumber++;
4 }
自定義執行任務Runnable
對象的構造方法
1public Thread(Runnable target) {
2 init(null, target, "Thread-" + nextThreadNum(), 0);
3}
與第一個構造方法區別在於能夠自定義Runnable
對象
自定義執行任務Runnable
對象和AccessControlContext
對象的構造方法
1 Thread(Runnable target, AccessControlContext acc) {
2 init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
3}
自定義線程組ThreadGroup
和執行任務Runnable
對象的構造方法
1public Thread(ThreadGroup group, Runnable target) {
2 init(group, target, "Thread-" + nextThreadNum(), 0);
3}
自定義線程名稱name
的構造方法
1 public Thread(String name) {
2 init(null, null, name, 0);
3}
自定義線程組ThreadGroup
和線程名稱name
的構造方法
1 public Thread(ThreadGroup group, String name) {
2 init(group, null, name, 0);
3}
自定義執行任務Runnable
對象和線程名稱name
的構造方法
1 public Thread(Runnable target, String name) {
2 init(null, target, name, 0);
3}
自定義線程組ThreadGroup
和線程名稱name
和執行任務Runnable
對象的構造方法
1 public Thread(ThreadGroup group, Runnable target, String name) {
2 init(group, target, name, 0);
3}
所有屬性都是自定義的構造方法
1 public Thread(ThreadGroup group, Runnable target, String name,
2 long stackSize) {
3 init(group, target, name, stackSize);
4}
Thread
提供了很是靈活的重載構造方法,方便開發者自定義各類參數的Thread
對象。
這裏記錄一些比較常見的方法吧,對於Thread
中存在的一些本地方法,咱們暫且不用管它~
設置線程名稱,該方法爲同步方法,爲了防止出現線程安全問題,能夠手動調用Thread
的實例方法設置名稱,也能夠在構造Thread
時在構造方法中傳入線程名稱,咱們一般都是在構造參數時設置
1 public final synchronized void setName(String name) {
2 //檢查安全權限
3 checkAccess();
4 //若是形參爲空,拋出空指針異常
5 if (name == null) {
6 throw new NullPointerException("name cannot be null");
7 }
8 //給當前線程對象設置名稱
9 this.name = name;
10 if (threadStatus != 0) {
11 setNativeName(name);
12 }
13 }
內部直接返回當前線程對象的名稱屬性
1 public final String getName() {
2 return name;
3 }
1public synchronized void start() {
2 //若是不是剛建立的線程,拋出異常
3 if (threadStatus != 0)
4 throw new IllegalThreadStateException();
5
6 //通知線程組,當前線程即將啓動,線程組當前啓動線程數+1,未啓動線程數-1
7 group.add(this);
8
9 //啓動標識
10 boolean started = false;
11 try {
12 //直接調用本地方法啓動線程
13 start0();
14 //設置啓動標識爲啓動成功
15 started = true;
16 } finally {
17 try {
18 //若是啓動呢失敗
19 if (!started) {
20 //線程組內部移除當前啓動的線程數量-1,同時啓動失敗的線程數量+1
21 group.threadStartFailed(this);
22 }
23 } catch (Throwable ignore) {
24 /* do nothing. If start0 threw a Throwable then
25 it will be passed up the call stack */
26 }
27 }
28 }
咱們正常的啓動線程都是調用Thread
的start()
方法,而後Java
虛擬機內部會去調用Thred
的run
方法,能夠看到Thread
類也是實現Runnable
接口,重寫了run
方法的:
1 @Override
2 public void run() {
3 //當前執行任務的Runnable對象不爲空,則調用其run方法
4 if (target != null) {
5 target.run();
6 }
7 }
Thread
的兩種使用方式:
Thread
類,重寫run
方法,那麼此時是直接執行run
方法的邏輯,不會使用target.run();
Runnable
接口,重寫run
方法,由於Java
的單繼承限制,一般使用這種方式建立線程更加靈活,這裏真正的執行邏輯就會交給自定義Runnable
去實現本質操做是設置daemon
屬性
1public final void setDaemon(boolean on) {
2 //檢查是否有安全權限
3 checkAccess();
4 //本地方法,測試此線程是否存活。, 若是一個線程已經啓動而且還沒有死亡,則該線程處於活動狀態
5 if (isAlive()) {
6 //若是線程先啓動後再設置守護線程,將拋出異常
7 throw new IllegalThreadStateException();
8 }
9 //設置當前守護線程屬性
10 daemon = on;
11 }
1 public final boolean isDaemon() {
2 //直接返回當前對象的守護線程屬性
3 return daemon;
4 }
先來個線程狀態圖:
獲取線程狀態:
1 public State getState() {
2 //由虛擬機實現,獲取當前線程的狀態
3 return sun.misc.VM.toThreadState(threadStatus);
4 }
線程狀態主要由內部枚舉類State
組成:
1 public enum State {
2
3 NEW,
4
5
6 RUNNABLE,
7
8
9 BLOCKED,
10
11
12 WAITING,
13
14
15 TIMED_WAITING,
16
17
18 TERMINATED;
19 }
synchronized
同步塊,就會進入此狀態,此時線程暫停執行,直到得到請求的鎖這是一個靜態的本地方法,使當前執行的線程休眠暫停執行 millis
毫秒,當休眠被中斷時會拋出InterruptedException
中斷異常
1 /**
2 * Causes the currently executing thread to sleep (temporarily cease
3 * execution) for the specified number of milliseconds, subject to
4 * the precision and accuracy of system timers and schedulers. The thread
5 * does not lose ownership of any monitors.
6 *
7 * @param millis
8 * the length of time to sleep in milliseconds
9 *
10 * @throws IllegalArgumentException
11 * if the value of {@code millis} is negative
12 *
13 * @throws InterruptedException
14 * if any thread has interrupted the current thread. The
15 * <i>interrupted status</i> of the current thread is
16 * cleared when this exception is thrown.
17 */
18 public static native void sleep(long millis) throws InterruptedException;
本地方法,測試此線程是否存活。 若是一個線程已經啓動而且還沒有死亡,則該線程處於活動狀態。
1 /**
2 * Tests if this thread is alive. A thread is alive if it has
3 * been started and has not yet died.
4 *
5 * @return <code>true</code> if this thread is alive;
6 * <code>false</code> otherwise.
7 */
8 public final native boolean isAlive();
1 /**
2 * Changes the priority of this thread.
3 * <p>
4 * First the <code>checkAccess</code> method of this thread is called
5 * with no arguments. This may result in throwing a
6 * <code>SecurityException</code>.
7 * <p>
8 * Otherwise, the priority of this thread is set to the smaller of
9 * the specified <code>newPriority</code> and the maximum permitted
10 * priority of the thread's thread group.
11 *
12 * @param newPriority priority to set this thread to
13 * @exception IllegalArgumentException If the priority is not in the
14 * range <code>MIN_PRIORITY</code> to
15 * <code>MAX_PRIORITY</code>.
16 * @exception SecurityException if the current thread cannot modify
17 * this thread.
18 * @see #getPriority
19 * @see #checkAccess()
20 * @see #getThreadGroup()
21 * @see #MAX_PRIORITY
22 * @see #MIN_PRIORITY
23 * @see ThreadGroup#getMaxPriority()
24 */
25 public final void setPriority(int newPriority) {
26 //線程組
27 ThreadGroup g;
28 //檢查安全權限
29 checkAccess();
30 //檢查優先級形參範圍
31 if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
32 throw new IllegalArgumentException();
33 }
34 if((g = getThreadGroup()) != null) {
35 //若是優先級形參大於線程組最大線程最大優先級
36 if (newPriority > g.getMaxPriority()) {
37 //則使用線程組的優先級數據
38 newPriority = g.getMaxPriority();
39 }
40 //調用本地設置線程優先級方法
41 setPriority0(priority = newPriority);
42 }
43 }
有一個stop()
實例方法能夠強制終止線程,不過這個方法由於太過於暴力,已經被標記爲過期方法,不建議程序員再使用,由於強制終止線程會致使數據不一致的問題。
這裏關於線程中斷的方法涉及三個:
1//實例方法,通知線程中斷,設置標誌位
2 public void interrupt(){}
3 //靜態方法,檢查當前線程的中斷狀態,同時會清除當前線程的中斷標誌位狀態
4 public static boolean interrupted(){}
5 //實例方法,檢查當前線程是否被中斷,實際上是檢查中斷標誌位
6 public boolean isInterrupted(){}
interrupt() 方法解析
1/**
2 * Interrupts this thread.
3 *
4 * <p> Unless the current thread is interrupting itself, which is
5 * always permitted, the {@link #checkAccess() checkAccess} method
6 * of this thread is invoked, which may cause a {@link
7 * SecurityException} to be thrown.
8 *
9 * <p> If this thread is blocked in an invocation of the {@link
10 * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
11 * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
12 * class, or of the {@link #join()}, {@link #join(long)}, {@link
13 * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
14 * methods of this class, then its interrupt status will be cleared and it
15 * will receive an {@link InterruptedException}.
16 *
17 * <p> If this thread is blocked in an I/O operation upon an {@link
18 * java.nio.channels.InterruptibleChannel InterruptibleChannel}
19 * then the channel will be closed, the thread's interrupt
20 * status will be set, and the thread will receive a {@link
21 * java.nio.channels.ClosedByInterruptException}.
22 *
23 * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
24 * then the thread's interrupt status will be set and it will return
25 * immediately from the selection operation, possibly with a non-zero
26 * value, just as if the selector's {@link
27 * java.nio.channels.Selector#wakeup wakeup} method were invoked.
28 *
29 * <p> If none of the previous conditions hold then this thread's interrupt
30 * status will be set. </p>
31 *
32 * <p> Interrupting a thread that is not alive need not have any effect.
33 *
34 * @throws SecurityException
35 * if the current thread cannot modify this thread
36 *
37 * @revised 6.0
38 * @spec JSR-51
39 */
40 public void interrupt() {
41 //檢查是不是自身調用
42 if (this != Thread.currentThread())
43 //檢查安全權限,這可能致使拋出{@link * SecurityException}。
44 checkAccess();
45
46 //同步代碼塊
47 synchronized (blockerLock) {
48 Interruptible b = blocker;
49 //檢查是不是阻塞線程調用
50 if (b != null) {
51 //設置線程中斷標誌位
52 interrupt0();
53 //此時拋出異常,將中斷標誌位設置爲false,此時咱們正常會捕獲該異常,從新設置中斷標誌位
54 b.interrupt(this);
55 return;
56 }
57 }
58 //如無心外,則正常設置中斷標誌位
59 interrupt0();
60 }
SecurityException
wait
,join
,sleep
等方法,會使當前線程進入阻塞狀態,此時有可能發生InterruptedException
異常檢查線程是否被中斷:
1 /**
2 * Tests whether this thread has been interrupted. The <i>interrupted
3 * status</i> of the thread is unaffected by this method.
4
5 測試此線程是否已被中斷。, 線程的<i>中斷*狀態</ i>不受此方法的影響。
6 *
7 * <p>A thread interruption ignored because a thread was not alive
8 * at the time of the interrupt will be reflected by this method
9 * returning false.
10 *
11 * @return <code>true</code> if this thread has been interrupted;
12 * <code>false</code> otherwise.
13 * @see #interrupted()
14 * @revised 6.0
15 */
16 public boolean isInterrupted() {
17 return isInterrupted(false);
18 }
靜態方法,會清空當前線程的中斷標誌位:
1 /**
2 *測試當前線程是否已被中斷。, 此方法清除線程的* <i>中斷狀態</ i>。, 換句話說,若是要連續兩次調用此方法,則* second調用將返回false(除非當前線程再次被中斷,在第一次調用已清除其中斷的*狀態 以後且在第二次調用已檢查以前), 它)
3 *
4 * <p>A thread interruption ignored because a thread was not alive
5 * at the time of the interrupt will be reflected by this method
6 * returning false.
7 *
8 * @return <code>true</code> if the current thread has been interrupted;
9 * <code>false</code> otherwise.
10 * @see #isInterrupted()
11 * @revised 6.0
12 */
13 public static boolean interrupted() {
14 return currentThread().isInterrupted(true);
15 }
記錄本身閱讀Thread
類源碼的一些思考,不過對於其中用到的不少本地方法只能望而卻步,還有一些代碼沒有看明白,暫且先這樣吧,若是有不足之處,請留言告知我,謝謝!後續會在實踐中對Thread
作出更多總結記錄。
因爲篇幅較長,暫且先記錄這些吧,後續會不按期更新原創文章,歡迎關注公衆號 「張少林同窗」!