從源碼的角度再學「Thread」

前言

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

Thread 經常使用屬性

閱讀一個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 構造方法

瞭解了屬性以後,看看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, nulltrue);
4    }

接下來看看全部構造方法:

  1. 空構造方法

    1 public Thread() {
    2        init(nullnull"Thread-" + nextThreadNum(), 0);
    3    }

    內部調用的是init第二個重載方法,參數基本都是默認值,線程名稱寫死爲"Thread-" + nextThreadNum()格式,nextThreadNum()爲一個同步方法,內部維護一個靜態屬性表示線程的初始化數量+1:

    1 private static int threadInitNumber;
    2    private static synchronized int nextThreadNum() {
    3        return threadInitNumber++;
    4    }
  2. 自定義執行任務Runnable對象的構造方法

    1public Thread(Runnable target) {
    2    init(null, target, "Thread-" + nextThreadNum(), 0);
    3}

    與第一個構造方法區別在於能夠自定義Runnable對象

  3. 自定義執行任務Runnable對象和AccessControlContext對象的構造方法

    1 Thread(Runnable target, AccessControlContext acc) {
    2    init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
    3}
  4. 自定義線程組ThreadGroup和執行任務Runnable對象的構造方法

    1public Thread(ThreadGroup group, Runnable target) {
    2    init(group, target, "Thread-" + nextThreadNum(), 0);
    3}
  5. 自定義線程名稱name的構造方法

    1 public Thread(String name) {
    2    init(nullnull, name, 0);
    3}
  6. 自定義線程組ThreadGroup和線程名稱name的構造方法

    1 public Thread(ThreadGroup group, String name) {
    2    init(group, null, name, 0);
    3}
  7. 自定義執行任務Runnable對象和線程名稱name的構造方法

    1 public Thread(Runnable target, String name) {
    2    init(null, target, name, 0);
    3}
  8. 自定義線程組ThreadGroup和線程名稱name和執行任務Runnable對象的構造方法

    1  public Thread(ThreadGroup group, Runnable target, String name) {
    2    init(group, target, name, 0);
    3}
  9. 所有屬性都是自定義的構造方法

    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    }

咱們正常的啓動線程都是調用Threadstart()方法,而後Java虛擬機內部會去調用Thredrun方法,能夠看到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    }
  • NEW:剛剛建立,還沒有啓動的線程處於此狀態
  • RUNNABLE:在Java虛擬機中執行的線程處於此狀態
  • BLOCKED:被阻塞等待監視器鎖的線程處於此狀態,好比線程在執行過程當中遇到synchronized同步塊,就會進入此狀態,此時線程暫停執行,直到得到請求的鎖
  • WAITING:無限期等待另外一個線程執行特定操做的線程處於此狀態
    • 經過 wait() 方法等待的線程在等待 notify() 方法
    • 經過 join() 方法等待的線程則會等待目標線程的終止
  • TIMED_WAITING:正在等待另外一個線程執行動做,直到指定等待時間的線程處於此狀態
    • 經過 wait() 方法,攜帶超時時間,等待的線程在等待 notify() 方法
    • 經過 join() 方法,攜帶超時時間,等待的線程則會等待目標線程的終止
  • TERMINATED:已退出的線程處於此狀態,此時線程沒法再回到 RUNNABLE 狀態

線程休眠

這是一個靜態的本地方法,使當前執行的線程休眠暫停執行 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作出更多總結記錄。

最後

因爲篇幅較長,暫且先記錄這些吧,後續會不按期更新原創文章,歡迎關注公衆號 「張少林同窗」!

相關文章
相關標籤/搜索