1. 程式人生 > >[java] Thread類詳解

[java] Thread類詳解

 

目錄

Thread類概述

成員變數

構造方法

start方法

run方法

Thread直接呼叫run()和start()方法的區別

sleep方法

yield方法

interrupt方法

join方法

getState方法

總結


本篇部落格基於jdk1.8

Thread類概述

Thread的一個例項,即是一個程式中執行的一個執行緒,java虛擬機器允許一個應用程式同時有多個併發的執行緒執行。

每一個執行緒都有一個優先順序。優先順序高的執行緒比優先順序低的執行緒優先執行。每一個執行緒也都可能被標記為一個守護執行緒。當代碼執行到某一個執行緒去建立另一個執行緒時,新建立的執行緒的優先順序會初始化成和建立它的執行緒一樣。並且當且僅當建立執行緒為守護執行緒時,被建立執行緒才會被設為守護執行緒。

當Java虛擬機器啟動時,通常會有一個單獨的非守護程序執行緒(通常這個非守護執行緒會去呼叫一個指定的類的main方法)。Java虛擬機器器繼續執行執行緒,直到以下任一一種情況發生:

Runtime類的exit方法被呼叫並且security manager(安全管理器)允許退出操作操作發生。

所有的非守護執行緒全部都已經終結,要麼通過從呼叫返回到執行方法或由丟擲超出執行的異常方法。

建立一個新的執行執行緒有兩種方法:

宣告一個Thread類的子類。這個子類應當去重寫Thread類裡的run方法。然後子類的例項就可以分配和啟動。例如,計算大於指定值的素數的執行緒可以寫成如下:

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();

建立執行緒的另一種方法是宣告一個類實現Runable介面,這個類需要實現run方法,然後這個類的例項就可以在建立執行緒時被當做引數傳遞進去,並且啟動,同一個例子的另一種樣式如下:

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();

每個執行緒都有用於標識目的的名稱。多個執行緒可能具有相同的名稱。如果建立一個執行緒時未指定名稱,則會為其生成一個新名稱。

除非另有說明,否則,向Thread類中的建構函式或方法傳遞null引數將導致丟擲NullPointerException。

另外一點特別有意思的是,Thread類本身就是實現了Runnable介面的一個類。

成員變數

    private volatile String name;   //執行緒的名字
    private int priority; //執行緒優先順序
    /* Whether or not to single_step this thread. */
    private boolean     single_step;

    /* Whether or not the thread is a daemon thread. */
    private boolean     daemon = false; //是否是守護執行緒

    /* What will be run. */
    private Runnable target; //將會被執行的Runnable.

    /* The group of this thread */
    private ThreadGroup group;  //這個執行緒的組

    /* The context ClassLoader for this thread */
    private ClassLoader contextClassLoader; //這個執行緒的上下文

    /* The inherited AccessControlContext of this thread */
    private AccessControlContext inheritedAccessControlContext; //繼承的請求控制

    /* For autonumbering anonymous threads. */
    private static int threadInitNumber;  //預設執行緒的自動編號
   
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null; //當前執行緒附屬的ThreadLocal,而ThreadLocalMap會被ThreadLocal維護)

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  // 主要作用:為子執行緒提供從父執行緒那裡繼承的值
  //在建立子執行緒時,子執行緒會接收所有可繼承的執行緒區域性變數的初始值,以獲得父執行緒所具有的值
  // 建立一個執行緒時如果儲存了所有 InheritableThreadLocal 物件的值,那麼這些值也將自動傳遞給子執行緒
  //如果一個子執行緒呼叫 InheritableThreadLocal 的 get() ,那麼它將與它的父執行緒看到同一個物件

    /*
     * The requested stack size for this thread, or 0 if the creator did
     * not specify a stack size.  It is up to the VM to do whatever it
     * likes with this number; some VMs will ignore it.
     */
    private long stackSize; //該執行緒請求的堆疊大小 預設一般都是忽略

    /*
     * Thread ID
     */
    private long tid;  // 每個執行緒都有專屬ID,但名字可能重複

    /* For generating thread ID */
    private static long threadSeqNumber;   //用來生成thread ID

    /* Java thread status for tools,
     * initialized to indicate thread 'not yet started'
     */

    private volatile int threadStatus = 0;  //標識執行緒狀態,預設是執行緒未啟動

    /* The object in which this thread is blocked in an interruptible I/O
     * operation, if any.  The blocker's interrupt method should be invoked
     * after setting this thread's interrupt status.
     */
    private volatile Interruptible blocker;  //阻塞器鎖,主要用於處理阻塞情況 

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;//執行緒的優先順序中最小的

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5; //執行緒的優先順序中第二的同時也是預設的優先順序

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10; //最高的優先順序

構造方法

據完全統計,Thread類裡一共提供了九個構造方法...其中有一個是非public的方法。

但是這九個構造方法事實上最後都是呼叫了一個叫做init的方法,該方法定義如下:

/**
     * Initializes a Thread.
     *
     * @param g the Thread group
     * @param target the object whose run() method gets called
     * @param name the name of the new Thread
     * @param stackSize the desired stack size for the new thread, or
     *        zero to indicate that this parameter is to be ignored.
     * @param acc the AccessControlContext to inherit, or
     *            AccessController.getContext() if null
     * @param inheritThreadLocals if {@code true}, inherit initial values for
     *            inheritable thread-locals from the constructing thread
     */
    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) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        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);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

該方法一共六個引數:

1.ThreadGroup 執行緒組

2.Runnable  target 一個Runnable物件,即呼叫run方法的物件

3.String name 新執行緒的名字

4.long stackSize 新執行緒分配所需堆疊的大小

5.AccessControlContext acc 許可權控制

6.boolean inheritThreadLocals 是否繼承ThreadLocals, 這個引數總是true。

方法的實現大致總結起來就是,給變數賦初值,大部分的初值都是通過該執行緒的父執行緒賦值,獲取父執行緒物件的程式碼為:

Thread parent = currentThread();

而currentThread方法的實現是一個本地方法。

start方法

start方法的作用就是讓該執行緒開始執行,java虛擬機器會呼叫這個執行緒的run方法。

執行start方法的結果是兩個執行緒會併發的執行,當前執行緒即執行start方法的執行緒,以及另一個呼叫run方法的執行緒。

啟動一個執行緒兩次永遠是非法的,會丟擲IllegalThreadStateException。程式碼如下:

 public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

可以看到上來會驗證執行緒的狀態,如果執行緒已啟動則拋異常。核心實現程式碼為方法start0,是一個本地方法,在這個本地方法的實現當中,虛擬機器會去呼叫該執行緒的run方法,在java程式碼中並看不出這一點。

這就是我們可以通過start方法啟動執行緒,並且run方法會執行的原因所在。

run方法

Thread類實現了Runable介面,自然也就實現了run方法,程式碼如下

  @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

target定義如下:

    /* What will be run. */
    private Runnable target;

target就是Runnable的一個例項,是Thread類中執行run的物件。注意:這裡的程式碼同時說明了我們new一個Thread的時候問什麼要傳入一個Runable的物件,因為事實上後來這個Thread啟動呼叫run方法的時候,就是呼叫的這個Runable的run方法。

問題:Thread直接呼叫run()和start()方法的區別?

 run()方法: 在本執行緒內呼叫該Runnable物件的run()方法,可以重複多次呼叫;

 start()方法:啟動一個執行緒,然後Java虛擬機器會呼叫該Runnable物件,即new一個Thread的時候傳入的Runable物件,的run()方法,不能多次啟動一個執行緒。

你呼叫run()方法的時候,只會是在原來的執行緒中呼叫,沒有新的執行緒啟動,start()方法才會啟動新執行緒。

 

sleep方法

有兩個過載的sleep方法。其中一個程式碼如下:

 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);
    }

在呼叫另一個sleep方法之前,就是做了一些range check,即範圍檢查,然後就直接呼叫了另一個native的sleep方法。

yield方法

程式碼如下

 public static native void yield();

作用是使當前執行緒從執行狀態(執行狀態)變為可執行態(就緒狀態)。也是一個native方法。

interrupt方法

public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

實現也是呼叫了一個interrupt0的本地方法,有一句關鍵的註釋:只是去set了interrupt flag,即不會真正的去停止執行緒,關於這一部分的內容,可以參考我的另一篇部落格:停止執行緒

join方法

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) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

join的作用就是使得當前執行緒阻塞,一直到呼叫join方法的執行緒物件執行完run方法中的任務且銷燬之後才繼續執行後面的程式碼。

從上面的程式碼實現來看,join方法是通過和wait方法配合實現的。

getState方法

該方法是返回執行緒的狀態,它的實現很有意思,如下:

 public State getState() {
        // get current thread state
        return sun.misc.VM.toThreadState(threadStatus);
    }
public static State toThreadState(int var0) {
        if ((var0 & 4) != 0) {
            return State.RUNNABLE;
        } else if ((var0 & 1024) != 0) {
            return State.BLOCKED;
        } else if ((var0 & 16) != 0) {
            return State.WAITING;
        } else if ((var0 & 32) != 0) {
            return State.TIMED_WAITING;
        } else if ((var0 & 2) != 0) {
            return State.TERMINATED;
        } else {
            return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
        }
    }

toThreadState方法的實現就是根據執行緒狀態的數字,返回對應的狀態而已,它們只是有一個mapping的關係。State的定義則是一個列舉,如下:

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

可以看到java的執行緒一共是有六種狀態的。

holdsLock方法判斷執行緒是否持有鎖物件

   /**
     * Returns <tt>true</tt> if and only if the current thread holds the
     * monitor lock on the specified object.
     *
     * <p>This method is designed to allow a program to assert that
     * the current thread already holds a specified lock:
     * <pre>
     *     assert Thread.holdsLock(obj);
     * </pre>
     *
     * @param  obj the object on which to test lock ownership
     * @throws NullPointerException if obj is <tt>null</tt>
     * @return <tt>true</tt> if the current thread holds the monitor lock on
     *         the specified object.
     * @since 1.4
     */
    public static native boolean holdsLock(Object obj);

為什麼wait和notify方法不在Thread類裡定義

需要說明為什麼把這些方法放在Object類裡是有意義的,還有不把它放在Thread類裡的原因。一個很明顯的原因是JAVA提供的鎖是物件級的而不是執行緒級的,每個物件都有鎖,通過執行緒獲得。如果執行緒需要等待某些鎖那麼呼叫物件中的wait()方法就有意義了。如果wait()方法定義在Thread類中,執行緒正在等待的是哪個鎖就不明顯了。簡單的說,由於wait,notify和notifyAll都是鎖級別的操作,所以把他們定義在Object類中因為鎖屬於物件

總結

經過上面的分析,可以發現事實上Thread類因為設計到執行緒的操作,因此大量的還是依靠了native方法實現。java核心邏輯並不多。