從源碼的角度再學「Thread」

前言

Java中的線程是使用Thread類實現的,Thread在初學Java的時候就學過了,也在實踐中用過,不過一直沒從源碼的角度去看過它的實現,今天從源碼的角度出發,再次學習Java Thread,願此後對Thread的實踐更加駕輕就熟。java

從註釋開始

相信閱讀過JDK源碼的同窗都能感覺到JDK源碼中有很是詳盡的註釋,閱讀某個類的源碼應當先看看註釋對它的介紹,註釋原文就不貼了,如下是我對它的總結:程序員

  • Thread是程序中執行的線程,Java虛擬機容許應用程序同時容許多個執行線程
  • 每一個線程都有優先級的概念,具備較高優先級的線程優先於優先級較低的線程執行
  • 每一個線程均可以被設置爲守護線程
  • 當在某個線程中運行的代碼建立一個新的Thread對象時,新的線程優先級跟建立線程一致
  • Java虛擬機啓動的時候都會啓動一個叫作main的線程,它沒有守護線程,main線程會繼續執行,直到如下狀況發送安全

    • Runtime 類的退出方法exit被調用而且安全管理器容許進行退出操做
    • 全部非守護線程均已死亡,或者run方法執行結束正常返回結果,或者run方法拋出異常
  • 建立線程第一種方式:繼承Thread類,重寫run方法less

    //定義線程類
    class PrimeThread extends Thread {
          long minPrime;
          PrimeThread(long minPrime) {
              this.minPrime = minPrime;
          }
          public void run() {
              // compute primes larger than minPrime
               . . .
          }
      }
    //啓動線程
    PrimeThread p = new PrimeThread(143);
    p.start();
  • 建立線程第二種方式:實現Runnable接口,重寫run方法,由於Java的單繼承限制,一般使用這種方式建立線程更加靈活ide

    //定義線程
     class PrimeRun implements Runnable {
          long minPrime;
          PrimeRun(long minPrime) {
              this.minPrime = minPrime;
          }
          public void run() {
              // compute primes larger than minPrime
               . . .
          }
      }
    //啓動線程
    PrimeRun p = new PrimeRun(143);
    new Thread(p).start();
  • 建立線程時能夠給線程指定名字,若是沒有指定,會自動爲它生成名字
  • 除非另有說明,不然將null參數傳遞給Thread類中的構造函數或方法將致使拋出 NullPointerException

Thread 經常使用屬性

閱讀一個Java類,先從它擁有哪些屬性入手:函數

//線程名稱,建立線程時能夠指定線程的名稱
private volatile String name;

//線程優先級,能夠設置線程的優先級
private int priority;

//能夠配置線程是否爲守護線程,默認爲false
private boolean daemon = false;

//最終執行線程任務的`Runnable`
private Runnable target;

//描述線程組的類
private ThreadGroup group;

//此線程的上下文ClassLoader
private ClassLoader contextClassLoader;

//全部初始化線程的數目,用於自動編號匿名線程,當沒有指定線程名稱時,會自動爲其編號
private static int threadInitNumber;

//此線程請求的堆棧大小,若是建立者沒有指定堆棧大小,則爲0。, 虛擬機能夠用這個數字作任何喜歡的事情。, 一些虛擬機會忽略它。
private long stackSize;

//線程id
private long tid;

//用於生成線程ID
private static long threadSeqNumber;

//線程狀態
private volatile int threadStatus = 0;

//線程能夠擁有的最低優先級
public final static int MIN_PRIORITY = 1;

//分配給線程的默認優先級。
public final static int NORM_PRIORITY = 5;

//線程能夠擁有的最大優先級
public final static int MAX_PRIORITY = 10;

全部的屬性命名都很語義化,其實已看名稱基本就猜到它是幹嗎的了,難度不大~~學習

Thread 構造方法

瞭解了屬性以後,看看Thread實例是怎麼構造的?先預覽下它大體有多少個構造方法:測試

查看每一個構造方法內部源碼,發現均調用的是名爲init的私有方法,再看init方法有兩個重載,而其核心方法以下:this

/**
     * Initializes a Thread.
     *
     * @param g                   線程組
     * @param target              最終執行任務的 `run()` 方法的對象
     * @param name                新線程的名稱
     * @param stackSize           新線程所需的堆棧大小,或者 0 表示要忽略此參數
     * @param acc                 要繼承的AccessControlContext,若是爲null,則爲 AccessController.getContext()
     * @param inheritThreadLocals 若是爲 true,從構造線程繼承可繼承的線程局部的初始值
     */
    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) {
            //若是安全管理器不爲空
            if (security != null) {
                //獲取SecurityManager中的線程組
                g = security.getThreadGroup();
            }
            //若是獲取的線程組仍是爲空
            if (g == null) {
                //則使用父線程的線程組
                g = parent.getThreadGroup();
            }
        }
        
        //檢查安全權限
        g.checkAccess();

        //使用安全管理器檢查是否有權限
        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()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        //初始化當前線程對象的最終執行任務對象
        this.target = target;
        //這裏再對線程的優先級字段進行處理
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        //初始化當前線程對象的堆棧大小
        this.stackSize = stackSize;

        //初始化當前線程對象的線程ID,該方法是同步的,內部其實是threadSeqNumber++
        tid = nextThreadID();
    }

另外一個重載init私有方法以下,實際上內部調用的是上述init方法:spa

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

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

  1. 空構造方法

    public Thread() {
            init(null, null, "Thread-" + nextThreadNum(), 0);
        }

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

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

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

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

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

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

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

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

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

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

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

    public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
        init(group, target, name, stackSize);
    }

Thread提供了很是靈活的重載構造方法,方便開發者自定義各類參數的Thread對象。

經常使用方法

這裏記錄一些比較常見的方法吧,對於Thread中存在的一些本地方法,咱們暫且不用管它~

設置線程名稱

設置線程名稱,該方法爲同步方法,爲了防止出現線程安全問題,能夠手動調用Thread的實例方法設置名稱,也能夠在構造Thread時在構造方法中傳入線程名稱,咱們一般都是在構造參數時設置

public final synchronized void setName(String name) {
         //檢查安全權限
          checkAccess();
         //若是形參爲空,拋出空指針異常
          if (name == null) {
              throw new NullPointerException("name cannot be null");
          }
        //給當前線程對象設置名稱
          this.name = name;
          if (threadStatus != 0) {
              setNativeName(name);
          }
      }

獲取線程名稱

內部直接返回當前線程對象的名稱屬性

public final String getName() {
        return name;
    }

啓動線程

public synchronized void start() {
        //若是不是剛建立的線程,拋出異常
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        //通知線程組,當前線程即將啓動,線程組當前啓動線程數+1,未啓動線程數-1
        group.add(this);
        
        //啓動標識
        boolean started = false;
        try {
            //直接調用本地方法啓動線程
            start0();
            //設置啓動標識爲啓動成功
            started = true;
        } finally {
            try {
                //若是啓動呢失敗
                if (!started) {
                    //線程組內部移除當前啓動的線程數量-1,同時啓動失敗的線程數量+1
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

咱們正常的啓動線程都是調用Threadstart()方法,而後Java虛擬機內部會去調用Thredrun方法,能夠看到Thread類也是實現Runnable接口,重寫了run方法的:

@Override
    public void run() {
        //當前執行任務的Runnable對象不爲空,則調用其run方法
        if (target != null) {
            target.run();
        }
    }

Thread的兩種使用方式:

  • 繼承Thread類,重寫run方法,那麼此時是直接執行run方法的邏輯,不會使用 target.run();
  • 實現Runnable接口,重寫run方法,由於Java的單繼承限制,一般使用這種方式建立線程更加靈活,這裏真正的執行邏輯就會交給自定義Runnable去實現

設置守護線程

本質操做是設置daemon屬性

public final void setDaemon(boolean on) {
        //檢查是否有安全權限
        checkAccess();
        //本地方法,測試此線程是否存活。, 若是一個線程已經啓動而且還沒有死亡,則該線程處於活動狀態
        if (isAlive()) {
            //若是線程先啓動後再設置守護線程,將拋出異常
            throw new IllegalThreadStateException();
        }
        //設置當前守護線程屬性
        daemon = on;
    }

判斷線程是否爲守護線程

public final boolean isDaemon() {
        //直接返回當前對象的守護線程屬性
        return daemon;
    }

線程狀態

先來個線程狀態圖:

獲取線程狀態:

public State getState() {
        //由虛擬機實現,獲取當前線程的狀態
        return sun.misc.VM.toThreadState(threadStatus);
    }

線程狀態主要由內部枚舉類State組成:

public enum State {
      
        NEW,

      
        RUNNABLE,

      
        BLOCKED,

       
        WAITING,

       
        TIMED_WAITING,

       
        TERMINATED;
    }
  • NEW:剛剛建立,還沒有啓動的線程處於此狀態
  • RUNNABLE:在Java虛擬機中執行的線程處於此狀態
  • BLOCKED:被阻塞等待監視器鎖的線程處於此狀態,好比線程在執行過程當中遇到synchronized同步塊,就會進入此狀態,此時線程暫停執行,直到得到請求的鎖
  • WAITING:無限期等待另外一個線程執行特定操做的線程處於此狀態

    • 經過 wait() 方法等待的線程在等待 notify() 方法
    • 經過 join() 方法等待的線程則會等待目標線程的終止
  • TIMED_WAITING:正在等待另外一個線程執行動做,直到指定等待時間的線程處於此狀態

    • 經過 wait() 方法,攜帶超時時間,等待的線程在等待 notify() 方法
    • 經過 join() 方法,攜帶超時時間,等待的線程則會等待目標線程的終止
  • TERMINATED:已退出的線程處於此狀態,此時線程沒法再回到 RUNNABLE 狀態

線程休眠

這是一個靜態的本地方法,使當前執行的線程休眠暫停執行 millis 毫秒,當休眠被中斷時會拋出InterruptedException中斷異常

/**
     * Causes the currently executing thread to sleep (temporarily cease
     * execution) for the specified number of milliseconds, subject to
     * the precision and accuracy of system timers and schedulers. The thread
     * does not lose ownership of any monitors.
     *
     * @param  millis
     *         the length of time to sleep in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public static native void sleep(long millis) throws InterruptedException;

檢查線程是否存活

本地方法,測試此線程是否存活。 若是一個線程已經啓動而且還沒有死亡,則該線程處於活動狀態。

/**
     * Tests if this thread is alive. A thread is alive if it has
     * been started and has not yet died.
     *
     * @return  <code>true</code> if this thread is alive;
     *          <code>false</code> otherwise.
     */
    public final native boolean isAlive();

線程優先級

  • 設置線程優先級
/**
     * Changes the priority of this thread.
     * <p>
     * First the <code>checkAccess</code> method of this thread is called
     * with no arguments. This may result in throwing a
     * <code>SecurityException</code>.
     * <p>
     * Otherwise, the priority of this thread is set to the smaller of
     * the specified <code>newPriority</code> and the maximum permitted
     * priority of the thread's thread group.
     *
     * @param newPriority priority to set this thread to
     * @exception  IllegalArgumentException  If the priority is not in the
     *               range <code>MIN_PRIORITY</code> to
     *               <code>MAX_PRIORITY</code>.
     * @exception  SecurityException  if the current thread cannot modify
     *               this thread.
     * @see        #getPriority
     * @see        #checkAccess()
     * @see        #getThreadGroup()
     * @see        #MAX_PRIORITY
     * @see        #MIN_PRIORITY
     * @see        ThreadGroup#getMaxPriority()
     */
    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);
        }
    }

線程中斷

有一個stop()實例方法能夠強制終止線程,不過這個方法由於太過於暴力,已經被標記爲過期方法,不建議程序員再使用,由於強制終止線程會致使數據不一致的問題。

這裏關於線程中斷的方法涉及三個:

//實例方法,通知線程中斷,設置標誌位
 public void interrupt(){}
 //靜態方法,檢查當前線程的中斷狀態,同時會清除當前線程的中斷標誌位狀態
 public static boolean interrupted(){}
 //實例方法,檢查當前線程是否被中斷,實際上是檢查中斷標誌位
 public boolean isInterrupted(){}

interrupt() 方法解析

/**
     * Interrupts this thread.
     *
     * <p> Unless the current thread is interrupting itself, which is
     * always permitted, the {@link #checkAccess() checkAccess} method
     * of this thread is invoked, which may cause a {@link
     * SecurityException} to be thrown.
     *
     * <p> If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.
     *
     * <p> If this thread is blocked in an I/O operation upon an {@link
     * java.nio.channels.InterruptibleChannel InterruptibleChannel}
     * then the channel will be closed, the thread's interrupt
     * status will be set, and the thread will receive a {@link
     * java.nio.channels.ClosedByInterruptException}.
     *
     * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
     * then the thread's interrupt status will be set and it will return
     * immediately from the selection operation, possibly with a non-zero
     * value, just as if the selector's {@link
     * java.nio.channels.Selector#wakeup wakeup} method were invoked.
     *
     * <p> If none of the previous conditions hold then this thread's interrupt
     * status will be set. </p>
     *
     * <p> Interrupting a thread that is not alive need not have any effect.
     *
     * @throws  SecurityException
     *          if the current thread cannot modify this thread
     *
     * @revised 6.0
     * @spec JSR-51
     */
    public void interrupt() {
        //檢查是不是自身調用
        if (this != Thread.currentThread())
            //檢查安全權限,這可能致使拋出{@link * SecurityException}。
            checkAccess();
        
        //同步代碼塊
        synchronized (blockerLock) {
            Interruptible b = blocker;
            //檢查是不是阻塞線程調用
            if (b != null) {
                //設置線程中斷標誌位
                interrupt0(); 
                //此時拋出異常,將中斷標誌位設置爲false,此時咱們正常會捕獲該異常,從新設置中斷標誌位
                b.interrupt(this);
                return;
            }
        }
        //如無心外,則正常設置中斷標誌位
        interrupt0();
    }
  • 線程中斷方法不會使線程當即退出,而是給線程發送一個通知,告知目標線程,有人但願你退出啦~
  • 只能由自身調用,不然可能會拋出 SecurityException
  • 調用中斷方法是由目標線程本身決定是否中斷,而若是同時調用了wait,join,sleep等方法,會使當前線程進入阻塞狀態,此時有可能發生InterruptedException異常
  • 被阻塞的線程再調用中斷方法是不合理的
  • 中斷不活動的線程不會產生任何影響

檢查線程是否被中斷:

/**
     * Tests whether this thread has been interrupted.  The <i>interrupted
     * status</i> of the thread is unaffected by this method.
     
     測試此線程是否已被中斷。, 線程的<i>中斷*狀態</ i>不受此方法的影響。
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if this thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see     #interrupted()
     * @revised 6.0
     */
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

靜態方法,會清空當前線程的中斷標誌位:

/**
     *測試當前線程是否已被中斷。, 此方法清除線程的* <i>中斷狀態</ i>。, 換句話說,若是要連續兩次調用此方法,則* second調用將返回false(除非當前線程再次被中斷,在第一次調用已清除其中斷的*狀態   以後且在第二次調用已檢查以前), 它)
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if the current thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see #isInterrupted()
     * @revised 6.0
     */
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

總結

記錄本身閱讀Thread類源碼的一些思考,不過對於其中用到的不少本地方法只能望而卻步,還有一些代碼沒有看明白,暫且先這樣吧,若是有不足之處,請留言告知我,謝謝!後續會在實踐中對Thread作出更多總結記錄。

最後

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

相關文章
相關標籤/搜索