Java 併發:學習Thread 類

Java 中 Thread類 的各種操作與線程的生命週期密不可分,瞭解線程的生命週期有助於對Thread類中的各方法的理解。一般來說,線程從最初的創建到最終的消亡,要經歷創建、就緒、運行、阻塞 和 消亡 五個狀態(想要了解線程進程的關係可以參考《Java併發背景》)。在線程的生命週期中,上下文切換通過存儲和恢復CPU狀態使得其能夠從中斷點恢復執行。結合 線程生命週期,本文最後詳細介紹了 Thread 各常用 API。特別地,在介紹會導致線程進入Waiting狀態(包括Timed Waiting狀態)的相關API時,在這裏特別關注兩個問題:

  • 客戶端調用該API後,是否會釋放鎖(如果此時擁有鎖的話);

  • 客戶端調用該API後,是否會交出CPU(一般情況下,線程進入Waiting狀態(包括Timed Waiting狀態)時都會交出CPU);

一. 線程的生命週期

  Java 中 Thread類 的具體操作與線程的生命週期密不可分,瞭解線程的生命週期有助於對Thread類中的各方法的理解。

  在 Java虛擬機 中,線程從最初的創建到最終的消亡,要經歷若干個狀態:創建(new)、就緒(runnable/start)、運行(running)、阻塞(blocked)、等待(waiting)、時間等待(time waiting) 和 消亡(dead/terminated)。

當我們需要線程來執行某個子任務時,就必須先創建一個線程。但是線程創建之後,不會立即進入就緒狀態,因爲線程的運行需要一些條件(比如程序計數器、Java棧、本地方法棧等),只有線程運行需要的所有條件滿足了,才進入就緒狀態。當線程進入就緒狀態後,不代表立刻就能獲取CPU執行時間,也許此時CPU正在執行其他的事情,因此它要等待。當得到CPU執行時間之後,線程便真正進入運行狀態。線程在運行狀態過程中,可能有多個原因導致當前線程不繼續運行下去,比如用戶主動讓線程睡眠(睡眠一定的時間之後再重新執行)、用戶主動讓線程等待,或者被同步塊阻塞,此時就對應着多個狀態:time waiting(睡眠或等待一定的時間)、waiting(等待被喚醒)、blocked(阻塞)。當由於突然中斷或者子任務執行完畢,線程就會被消亡。

  實際上,Java只定義了六種線程狀態,分別是 New, Runnable, Waiting,Timed Waiting、Blocked 和 Terminated。爲形象表達線程從創建到消亡之間的狀態,下圖將Runnable狀態分成兩種狀態:正在運行狀態和就緒狀態.

 

二. 上下文切換

  以單核CPU爲例,CPU在一個時刻只能運行一個線程。CPU在運行一個線程的過程中,轉而去運行另外一個線程,這個叫做線程 上下文切換(對於進程也是類似)。

  由於可能當前線程的任務並沒有執行完畢,所以在切換時需要保存線程的運行狀態,以便下次重新切換回來時能夠緊接着之前的狀態繼續運行。舉個簡單的例子:比如,一個線程A正在讀取一個文件的內容,正讀到文件的一半,此時需要暫停線程A,轉去執行線程B,當再次切換回來執行線程A的時候,我們不希望線程A又從文件的開頭來讀取。

  因此需要記錄線程A的運行狀態,那麼會記錄哪些數據呢?因爲下次恢復時需要知道在這之前當前線程已經執行到哪條指令了,所以需要記錄程序計數器的值,另外比如說線程正在進行某個計算的時候被掛起了,那麼下次繼續執行的時候需要知道之前掛起時變量的值時多少,因此需要記錄CPU寄存器的狀態。所以,一般來說,線程上下文切換過程中會記錄程序計數器、CPU寄存器狀態等數據。

  實質上, 線程的上下文切換就是存儲和恢復CPU狀態的過程,它使得線程執行能夠從中斷點恢復執行,這正是有程序計數器所支持的。

  雖然多線程可以使得任務執行的效率得到提升,但是由於在線程切換時同樣會帶來一定的開銷代價,並且多個線程會導致系統資源佔用的增加,所以在進行多線程編程時要注意這些因素。

三. 線程的創建

  在 Java 中,創建線程去執行子任務一般有兩種方式:繼承 Thread 類和實現 Runnable 接口。其中,Thread 類本身就實現了 Runnable 接口,而使用繼承 Thread 類的方式創建線程的最大侷限就是不支持多繼承。特別需要注意兩點,

  • 實現多線程必須重寫run()方法,即在run()方法中定義需要執行的任務;
  • run()方法不需要用戶來調用。

代碼:

public class ThreadTest {
    public static void main(String[] args) {

        //使用繼承Thread類的方式創建線程
        new Thread(){
            @Override
            public void run() {
                System.out.println("Thread");
            }
        }.start();

        //使用實現Runnable接口的方式創建線程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Runnable");
            }
        });
        thread.start();

        //JVM 創建的主線程 main
        System.out.println("main");
    }
}
/* Output: (代碼的運行結果與代碼的執行順序或調用順序無關)
        Thread
        main
        Runnable
 */

創建好自己的線程類之後,就可以創建線程對象了,然後通過start()方法去啓動線程。注意,run() 方法中只是定義需要執行的任務,並且其不需要用戶來調用。當通過start()方法啓動一個線程之後,若線程獲得了CPU執行時間,便進入run()方法體去執行具體的任務。如果用戶直接調用run()方法,即相當於在主線程中執行run()方法,跟普通的方法調用沒有任何區別,此時並不會創建一個新的線程來執行定義的任務。實際上,start()方法的作用是通知 “線程規劃器” 該線程已經準備就緒,以便讓系統安排一個時間來調用其 run()方法,也就是使線程得到運行。Thread 類中的 run() 方法定義爲:

 /* What will be run. */
    private Runnable target;  // 類 Thread 的成員

    /**
     * If this thread was constructed using a separate <code>Runnable</code> run object, 
     * then that <code>Runnable</code> object's <code>run</code> method is called; otherwise, 
     * this method does nothing and returns. 
     * 
     * Subclasses of <code>Thread</code> should override this method. 
     *
     */
    public void run() {
        if (target != null) {
            target.run();
        }
    }

四. Thread 類詳解

  Thread 類實現了 Runnable 接口,在 Thread 類中,有一些比較關鍵的屬性,比如name是表示Thread的名字,可以通過Thread類的構造器中的參數來指定線程名字,priority表示線程的優先級(最大值爲10,最小值爲1,默認值爲5),daemon表示線程是否是守護線程,target表示要執行的任務。

(只截取前部分Thread類)

public class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }

    private volatile String name;
    private int            priority;
    private Thread         threadQ;
    private long           eetop;

    /* 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;

    /* JVM state */
    private boolean     stillborn = false;

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

    /* The group of this thread */
    private ThreadGroup group;

    /* The context ClassLoader for this thread */
    private ClassLoader contextClassLoader;

1、與線程運行狀態有關的方法

 1) start 方法

 start() 用來啓動一個線程,當調用該方法後,相應線程就會進入就緒狀態,該線程中的run()方法會在某個時機被調用。

 2)run 方法

 run()方法是不需要用戶來調用的。當通過start()方法啓動一個線程之後,一旦線程獲得了CPU執行時間,便進入run()方法體去執行具體的任務。注意,創建線程時必須重寫run()方法,以定義具體要執行的任務。

 /**
     * If this thread was constructed using a separate
     * <code>Runnable</code> run object, then that
     * <code>Runnable</code> object's <code>run</code> method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of <code>Thread</code> should override this method.該方法需要重寫
     *
     * @see     #start()
     * @see     #stop()
     * @see     #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

上訴代碼時Thread類中的run()方法的默認實現,其中target是Thread類的成員對象,是Runnable類型,通過Thread的構造函數進行賦值,實例化.

一般來說,有兩種方式可以達到重寫run()方法的效果:

  • 直接重寫:直接繼承Thread類並重寫run()方法;

  • 間接重寫:通過Thread構造函數傳入Runnable對象 (注意,實際上重寫的是 Runnable對象 的run() 方法)。

3)sleep 方法

  方法 sleep() 的作用是在指定的毫秒數內讓當前正在執行的線程(即 currentThread() 方法所返回的線程)睡眠,並交出 CPU 讓其去執行其他的任務。當線程睡眠時間滿後,不一定會立即得到執行,因爲此時 CPU 可能正在執行其他的任務。所以說,調用sleep方法相當於讓線程進入阻塞狀態。該方法有如下兩條特徵:

  • 如果調用了sleep方法,必須捕獲InterruptedException異常或者將該異常向上層拋出;
  • sleep方法不會釋放鎖,也就是說如果當前線程持有對某個對象的鎖,則即使調用sleep方法,其他線程也無法訪問這個對象。
 /**
     * 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;

4)yield 方法

  調用 yield()方法會讓當前線程交出CPU資源,讓CPU去執行其他的線程。但是,yield()不能控制具體的交出CPU的時間。需要注意的是,

  • yield()方法只能讓 擁有相同優先級的線程 有獲取 CPU 執行時間的機會;
  • 調用yield()方法並不會讓線程進入阻塞狀態,而是讓線程重回就緒狀態,它只需要等待重新得到 CPU 的執行;
  • 它同樣不會釋放鎖。
   /**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
     */
    public static native void yield();

代碼演示:

public class MyThread extends Thread {

    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 50000; i++) {
            Thread.yield();             // 將該語句註釋後,執行會變快
            count = count + (i + 1);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("用時:" + (endTime - beginTime) + "毫秒!");
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

5)join 方法
 
  假如在main線程中調用thread.join方法,則main線程會等待thread線程執行完畢或者等待一定的時間。詳細地,如果調用的是無參join方法,則等待thread執行完畢;如果調用的是指定了時間參數的join方法,則等待一定的時間。join()方法有三個重載版本:

public final synchronized void join(long millis) throws InterruptedException {...}
public final synchronized void join(long millis, int nanos) throws InterruptedException {...}
public final void join() throws InterruptedException {...}

以 join(long millis) 方法爲例,其內部調用了Object的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) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

根據以上源代碼可以看出,join()方法是通過wait()方法 (Object 提供的方法) 實現的。當 millis == 0 時,會進入 while(isAlive()) 循環,並且只要子線程是活的,宿主線程就不停的等待。 wait(0) 的作用是讓當前線程(宿主線程)等待,而這裏的當前線程是指 Thread.currentThread() 所返回的線程。所以,雖然是子線程對象(鎖)調用wait()方法,但是阻塞的是宿主線程。

看下面的例子,當 main線程 運行到 thread1.join() 時,main線程會獲得線程對象thread1的鎖(wait 意味着拿到該對象的鎖)。只要 thread1線程 存活, 就會調用該對象鎖的wait()方法阻塞 main線程,直至 thread1線程 退出纔會使 main線程 得以繼續執行。

public class Test {

    public static void main(String[] args) throws IOException  {
        System.out.println("進入線程"+Thread.currentThread().getName());
        Test test = new Test();
        MyThread thread1 = test.new MyThread();
        thread1.start();
        try {
            System.out.println("線程"+Thread.currentThread().getName()+"等待");
            thread1.join();
            System.out.println("線程"+Thread.currentThread().getName()+"繼續執行");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    } 

    class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println("進入線程"+Thread.currentThread().getName());
            try {
                Thread.currentThread().sleep(5000);
            } catch (InterruptedException e) {
                // TODO: handle exception
            }
            System.out.println("線程"+Thread.currentThread().getName()+"執行完畢");
        }
    }
}
/* Output:
        進入線程main
        線程main等待
        進入線程Thread-0
        線程Thread-0執行完畢
        線程main繼續執行
 */

看上面的例子,當 main線程 運行到 thread1.join() 時,main線程會獲得線程對象thread1的鎖(wait 意味着拿到該對象的鎖)。只要 thread1線程 存活, 就會調用該對象鎖的wait()方法阻塞 main線程。那麼,main線程被什麼時候喚醒呢?事實上,有wait就必然有notify。在整個jdk裏面,我們都不會找到對thread1線程的notify操作。這就要看jvm代碼了:

作者:cao
鏈接:https://www.zhihu.com/question/44621343/answer/97640972
來源:知乎

//一個c++函數:
void JavaThread::exit(bool destroy_vm, ExitType exit_type) ;

//這個函數的作用就是在一個線程執行完畢之後,jvm會做的收尾工作。裏面有一行代碼:ensure_join(this);

該函數源碼如下:

static void ensure_join(JavaThread* thread) {
    Handle threadObj(thread, thread->threadObj());

    ObjectLocker lock(threadObj, thread);

    thread->clear_pending_exception();

    java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);

    java_lang_Thread::set_thread(threadObj(), NULL);

    //thread就是當前線程,就是剛纔說的thread1線程。
    lock.notify_all(thread);

    thread->clear_pending_exception();
}

至此,thread1線程對象鎖調用了notifyall,那麼main線程也就能繼續跑下去了。

由於 join方法 會調用 wait方法 讓宿主線程進入阻塞狀態,並且會釋放線程佔有的鎖,並交出CPU執行權限。結合 join 方法的聲明,有以下三條:

  • join方法同樣會會讓線程交出CPU執行權限;
  • join方法同樣會讓線程釋放對一個對象持有的鎖;
  • 如果調用了join方法,必須捕獲InterruptedException異常或者將該異常向上層拋出。  

6)interrupt 方法

 interrupt,顧名思義,即中斷的意思。單獨調用interrupt方法可以使得 處於阻塞狀態的線程 拋出一個異常,也就是說,它可以用來中斷一個正處於阻塞狀態的線程;另外,通過 interrupted()方法 和 isInterrupted()方法 可以停止正在運行的線程。interrupt 方法在 JDK 中的定義爲:

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

interrupted() 和 isInterrupted()方法在 JDK 中的定義分別爲: 

 /**
     * Tests whether the current thread has been interrupted.  The
     * <i>interrupted status</i> of the thread is cleared by this method.  In
     * other words, if this method were to be called twice in succession, the
     * second call would return false (unless the current thread were
     * interrupted again, after the first call had cleared its interrupted
     * status and before the second call had examined it).
     *
     * <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);
    }

    /**
     * Tests whether this thread has been interrupted.  The <i>interrupted
     * status</i> of the thread is unaffected by this method.
     *
     * <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);
    }
  • interrupted()是靜態方法,作用對象:currentThread()方法所返回的線程
  • isInterrupted()實例方法,作用對象:本Tread對象

看下面的例子:

public class Test {

    public static void main(String[] args) throws IOException  {
        Test test = new Test();
        MyThread thread = test.new MyThread();
        thread.start();
        try {
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e) {

        }
        thread.interrupt();
    } 

    class MyThread extends Thread{
        @Override
        public void run() {
            try {
                System.out.println("進入睡眠狀態");
                Thread.currentThread().sleep(10000);
                System.out.println("睡眠完畢");
            } catch (InterruptedException e) {
                System.out.println("得到中斷異常");
            }
            System.out.println("run方法執行完畢");
        }
    }
}/* Output:
        進入睡眠狀態
        得到中斷異常
        run方法執行完畢
 */

從這裏可以看出,通過interrupt方法可以中斷處於阻塞狀態的線程。那麼能不能中斷處於非阻塞狀態的線程呢?看下面這個例子:

public class Test {

    public static void main(String[] args) throws IOException  {
        Test test = new Test();
        MyThread thread = test.new MyThread();
        thread.start();
        try {
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e) {}
        thread.interrupt();
    } 

    class MyThread extends Thread{
        @Override
        public void run() {
            int i = 0;
            while(i<Integer.MAX_VALUE){
                System.out.println(i+" while循環");
                i++;
            }
        }
    }
}

 運行該程序會發現,while循環會一直運行直到變量i的值超出Integer.MAX_VALUE。所以說,直接調用interrupt() 方法不能中斷正在運行中的線程。但是,如果配合 isInterrupted()/interrupted() 能夠中斷正在運行的線程,因爲調用interrupt()方法相當於將中斷標誌位置爲true,那麼可以通過調用isInterrupted()/interrupted()判斷中斷標誌是否被置位來中斷線程的執行。比如下面這段代碼:

public class Test {
    public static void main(String[] args) throws IOException  {
        Test test = new Test();
        MyThread thread = test.new MyThread();
        thread.start();
        try {
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e) {

        }
        thread.interrupt();
    } 

    class MyThread extends Thread{
        @Override
        public void run() {
            int i = 0;
            while(!isInterrupted() && i<Integer.MAX_VALUE){
                System.out.println(i+" while循環");
                i++;
            }
        }
    }
}

但是,一般情況下,不建議通過這種方式來中斷線程,一般會在MyThread類中增加一個 volatile 屬性 isStop 來標誌是否結束 while 循環,然後再在 while 循環中判斷 isStop 的值。例如

class MyThread extends Thread{
        private volatile boolean isStop = false;
        @Override
        public void run() {
            int i = 0;
            while(!isStop){
                i++;
            }
        }

        public void setStop(boolean stop){
            this.isStop = stop;
        }
    }

那麼,就可以在外面通過調用setStop方法來終止while循環。

 7)stop方法

 stop() 方法已經是一個 廢棄的 方法,它是一個 不安全的 方法。因爲調用 stop() 方法會直接終止run方法的調用,並且會拋出一個ThreadDeath錯誤,如果線程持有某個對象鎖的話,會完全釋放鎖,導致對象狀態不一致。所以, stop() 方法基本是不會被用到的。

8、線程的暫停與恢復

1) 線程的暫停、恢復方法在 JDK 中的定義

  暫停線程意味着此線程還可以恢復運行。在 Java 中,我可以使用 suspend() 方法暫停線程,使用 resume() 方法恢復線程的執行,但是這兩個方法已被廢棄,因爲它們具有固有的死鎖傾向。如果目標線程掛起時在保護關鍵系統資源的監視器上保持有鎖,則在目標線程重新開始以前,任何線程都不能訪問該資源。如果重新開始目標線程的線程想在調用 resume 之前鎖定該監視器,則會發生死鎖。

實例方法 suspend() 在類Thread中的定義和實例方法 resume() 在類Thread中的定義:

 /**
     * Suspends 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>(in the current thread).
     * <p>
     * If the thread is alive, it is suspended and makes no further
     * progress unless and until it is resumed.
     *
     * @exception  SecurityException  if the current thread cannot modify
     *               this thread.
     * @see #checkAccess
     * @deprecated   This method has been deprecated, as it is
     *   inherently deadlock-prone.  If the target thread holds a lock on the
     *   monitor protecting a critical system resource when it is suspended, no
     *   thread can access this resource until the target thread is resumed. If
     *   the thread that would resume the target thread attempts to lock this
     *   monitor prior to calling <code>resume</code>, deadlock results.  Such
     *   deadlocks typically manifest themselves as "frozen" processes.
     *   For more information, see
     *   <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why
     *   are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
     */
    @Deprecated
    public final void suspend() {
        checkAccess();
        suspend0();
    }

    /**
     * Resumes a suspended 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> (in the current thread).
     * <p>
     * If the thread is alive but suspended, it is resumed and is
     * permitted to make progress in its execution.
     *
     * @exception  SecurityException  if the current thread cannot modify this
     *               thread.
     * @see        #checkAccess
     * @see        #suspend()
     * @deprecated This method exists solely for use with {@link #suspend},
     *     which has been deprecated because it is deadlock-prone.
     *     For more information, see
     *     <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why
     *     are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
     */
    @Deprecated
    public final void resume() {
        checkAccess();
        resume0();
    }

2) 死鎖

  具體地,在使用 suspend 和 resume 方法時,如果使用不當,極易造成公共的同步對象的獨佔,使得其他線程無法得到公共同步對象鎖,從而造成死鎖。下面舉兩個示例:

// 示例 1
public class SynchronizedObject {

    public synchronized void printString() {        // 同步方法
        System.out.println("Thread-" + Thread.currentThread().getName() + " begins.");
        if (Thread.currentThread().getName().equals("a")) {
            System.out.println("線程a suspend 了...");
            Thread.currentThread().suspend();
        }
        System.out.println("Thread-" + Thread.currentThread().getName() + " is end.");
    }

    public static void main(String[] args) throws InterruptedException {

        final SynchronizedObject object = new SynchronizedObject();     // 兩個線程使用共享同一個對象

        Thread a = new Thread("a") {
            @Override
            public void run() {
                object.printString();
            }
        };
        a.start();

        new Thread("b") {
            @Override
            public void run() {
                System.out.println("thread2 啓動了,在等待中(發生“死鎖”)...");
                object.printString();
            }
        }.start();

        System.out.println("main 線程睡眠 " + 5 +" 秒...");
        Thread.sleep(5000);
        System.out.println("main 線程睡醒了...");

        a.resume();
        System.out.println("線程 a resume 了...");
    }
}/* Output:
        Thread-a begins.
        線程a suspend 了...
        thread2 啓動了,在等待中(發生死鎖)...
        main 線程睡眠 5 秒...
        main 線程睡醒了...
        線程 a resume 了...
        Thread-a is end.
        Thread-b begins.
        Thread-b is end.
 */

在示例 2 中,特別要注意的是,println() 方法實質上是一個同步方法。如果 thread 線程剛好在執行打印語句時被掛起,那麼將會導致 main線程中的字符串 “main end!” 遲遲不能打印。其中,println() 方法定義如下:

 

 

 

// 示例 2
public class MyThread extends Thread {

    private long i = 0;

    @Override
    public void run() {
        while (true) {
            i++;
            System.out.println(i);
        }
    }

    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(1);
            thread.suspend();
            System.out.println("main end!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2、線程常用操作

 1)、獲得代碼調用者信息

 currentThread() 方法返回代碼段正在被哪個線程調用的信息。其在 Thread類 中定義如下:

public class CountOperate extends Thread {

    public CountOperate() {
        super("Thread-CO");     // 線程 CountOperate 的名字
        System.out.println("CountOperate---begin");
        System.out.println("Thread.currentThread().getName()="
                + Thread.currentThread().getName());        
        System.out.println("this.getName()=" + this.getName());       
        System.out.println("CountOperate---end");
    }

    @Override
    public void run() {
        System.out.println("run---begin");
        System.out.println("Thread.currentThread().getName()="
                + Thread.currentThread().getName());    
        System.out.println("this.getName()=" + this.getName());    
        System.out.println("run---end");
    }

    public static void main(String[] args) {
        CountOperate c = new CountOperate();
        Thread t1 = new Thread(c);
        t1.setName("A");
        t1.start();         
        c.start();         
    }
}/* Output:(輸出結果不唯一)
        CountOperate---begin                             ....... 行 1
        Thread.currentThread().getName()=main            ....... 行 2
        this.getName()=Thread-CO                         ....... 行 3
        CountOperate---end                               ....... 行 4
        run---begin                                      ....... 行 5
        Thread.currentThread().getName()=A               ....... 行 6
        run---begin                                      ........行 7
        Thread.currentThread().getName()=Thread-CO       ....... 行 8
        this.getName()=Thread-CO                         ....... 行 9
        run---end                                        ....... 行 10
        this.getName()=Thread-CO                         ....... 行 11
        run---end                                        ....... 行 12
 */

 首先來看前四行的輸出。我們知道 CountOperate 繼承了 Thread 類,那麼 CountOperate 就得到了 Thread類的所有非私有屬性和方法。CountOperate 構造方法中的 super(“Thread-CO”);意味着調用了父類Thread的構造器Thread(String name),也就是爲 CountOperate線程 賦了標識名。由於該構造方法是由main()方法調用的,因此此時 Thread.currentThread() 返回的是main線程;而 this.getName() 返回的是CountOperate線程的標識名。

  其次,在main線程啓動了t1線程之後,CPU會在某個時機執行類CountOperate的run()方法。此時,Thread.currentThread() 返回的是t1線程,因爲是t1線程的啓動使run()方法得到了執行;而 this.getName() 返回的仍是CountOperate線程的標識名,因爲此時this指的是傳進來的CountOperate對象(具體原因見上面對run()方法的介紹),由於它本身也是一個線程對象,所以可以調用getName()得到相應的標識名。

  在main線程啓動了CountOperate線程之後,CPU也會在某個時機執行類該線程的run()方法。此時,Thread.currentThread() 返回的是CountOperate線程,因爲是CountOperate線程的啓動使run()方法得到了執行;而 this.getName() 返回的仍是CountOperate線程的標識名,因爲此時this指的就是剛剛創建的CountOperate對象本身,所以得到的仍是 “Thread-CO ”。

2)、判斷線程是否處於活動狀態

  方法 isAlive() 的功能是判斷調用該方法的線程是否處於活動狀態。其中,活動狀態指的是線程已經 start (無論是否獲得CPU資源並運行) 且尚未結束。 

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

下面的例子給出了 isAlive() 方法的使用方式:

public class CountOperate extends Thread {

    public CountOperate() {
        System.out.println("CountOperate---begin");

        System.out.println("Thread.currentThread().getName()="
                + Thread.currentThread().getName());        // main
        System.out.println("Thread.currentThread().isAlive()="
                + Thread.currentThread().isAlive());        // true

        System.out.println("this.getName()=" + this.getName());         // Thread-0
        System.out.println("this.isAlive()=" + this.isAlive());         // false

        System.out.println("CountOperate---end");
    }

    @Override
    public void run() {
        System.out.println("run---begin");

        System.out.println("Thread.currentThread().getName()="
                + Thread.currentThread().getName());        // A
        System.out.println("Thread.currentThread().isAlive()="
                + Thread.currentThread().isAlive());        // true

        System.out.println("this.getName()=" + this.getName());     // Thread-0
        System.out.println("this.isAlive()=" + this.isAlive());     // false

        System.out.println("run---end");
    }

    public static void main(String[] args) {
        CountOperate c = new CountOperate();
        Thread t1 = new Thread(c);
        System.out.println("main begin t1 isAlive=" + t1.isAlive());        // false
        t1.setName("A");
        t1.start();
        System.out.println("main end t1 isAlive=" + t1.isAlive());      // true
    }
}

3)、獲取線程唯一標識

  方法 getId() 的作用是取得線程唯一標識,由JVM自動給出。

/**
     * Returns the identifier of this Thread.  The thread ID is a positive
     * <tt>long</tt> number generated when this thread was created.
     * The thread ID is unique and remains unchanged during its lifetime.
     * When a thread is terminated, this thread ID may be reused.
     *
     * @return this thread's ID.
     * @since 1.5
     */
    public long getId() {
        return tid;
    }
// 示例
public class Test {
    public static void main(String[] args) {
        Thread runThread = Thread.currentThread();
        System.out.println(runThread.getName() + " " + runThread.getId());
    }
}/* Output:
        main 1
 */

4)、getName和setName

  用來得到或者設置線程名稱。如果我們不手動設置線程名字,JVM會爲該線程自動創建一個標識名,形式爲: Thread-數字。

5)、getPriority和setPriority

  在操作系統中,線程可以劃分優先級,優先級較高的線程得到的CPU資源較多,也就是CPU優先執行優先級較高的線程。設置線程優先級有助於幫助 “線程規劃器” 確定在下一次選擇哪個線程來獲得CPU資源。特別地,在 Java 中,線程的優先級分爲 1 ~ 10 這 10 個等級,如果小於 1 或大於 10,則 JDK 拋出異常 IllegalArgumentException ,該異常是 RuntimeException 的子類,屬於不受檢異常。JDK 中使用 3 個常量來預置定義優先級的值,如下:

public static final int MIN_PRIORITY = 1; 
public static final int NORM_PRIORITY = 5; 
public static final int MAX_PRIORITY = 10; 

在 Thread類中,方法 setPriority() 的定義爲:

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

(1). 線程優先級的繼承性

  在 Java 中,線程的優先級具有繼承性,比如 A 線程啓動 B 線程, 那麼 B 線程的優先級與 A 是一樣的

class MyThread2 extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread2 run priority=" + this.getPriority());
    }
}

public class MyThread1 extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread1 run priority=" + this.getPriority());
        MyThread2 thread2 = new MyThread2();
        thread2.start();
    }

    public static void main(String[] args) {
        System.out.println("main thread begin priority="
                + Thread.currentThread().getPriority());
        Thread.currentThread().setPriority(6);
        System.out.println("main thread end   priority="
                + Thread.currentThread().getPriority());
        MyThread1 thread1 = new MyThread1();
        thread1.start();
    }
}/* Output:
        main thread begin priority=5
        main thread end   priority=6
        MyThread1 run priority=6
        MyThread2 run priority=6
 */

(2). 線程優先級的規則性和隨機性

  線程的優先級具有一定的規則性,也就是CPU儘量將執行資源讓給優先級比較高的線程。特別地,高優先級的線程總是大部分先執行完,但並不一定所有的高優先級線程都能先執行完。

6)、守護線程 (Daemon)

  在 Java 中,線程可以分爲兩種類型,即用戶線程和守護線程。守護線程是一種特殊的線程,具有“陪伴”的含義:當進程中不存在非守護線程時,則守護線程自動銷燬,典型的守護線程就是垃圾回收線程。任何一個守護線程都是整個JVM中所有非守護線程的保姆,只要當前JVM實例中存在任何一個非守護線程沒有結束,守護線程就在工作;只有當最後一個非守護線程結束時,守護線程才隨着JVM一同結束工作。 在 Thread類中,方法 setDaemon() 的定義爲:

  /**
     * Marks this thread as either a {@linkplain #isDaemon daemon} thread
     * or a user thread. The Java Virtual Machine exits when the only
     * threads running are all daemon threads.
     *
     * <p> This method must be invoked before the thread is started.
     *
     * @param  on
     *         if {@code true}, marks this thread as a daemon thread
     *
     * @throws  IllegalThreadStateException
     *          if this thread is {@linkplain #isAlive alive}
     *
     * @throws  SecurityException
     *          if {@link #checkAccess} determines that the current
     *          thread cannot modify this thread
     */
    public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }
public class MyThread extends Thread {

    private int i = 0;

    @Override
    public void run() {
        try {
            while (true) {
                i++;
                System.out.println("i=" + (i));
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.setDaemon(true);       //設置爲守護線程
            thread.start();
            Thread.sleep(3000);
            System.out.println("main 線程結束,也意味着守護線程 thread 將要結束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}/* Output: (結果不唯一)
        i=1
        i=2
        i=3
        main 線程結束,也意味着守護線程 thread 將要結束
 */

小結:

1). 對於上述線程的各項基本操作,其 所操作的對象 滿足:

  • 若該操作是靜態方法,也就是說,該方法屬於類而非具體的某個對象,那麼該操作的作用對象就是 currentThread() 方法所返回 Thread 對象;
  • 若該操作是實例方法,也就是說,該方法屬於對象,那麼該操作的作用對象就是調用該方法的 Thread 對象。

2). 對於上述線程的各項基本操作,有:

  • 線程一旦被阻塞,就會釋放 CPU;
  • 當線程出現異常且沒有捕獲處理時,JVM會自動釋放當前線程佔用的鎖,因此不會由於異常導致出現死鎖現象。
  • 對於一個線程,CPU 的釋放 與 鎖的釋放沒有必然聯繫。

3). Thread類 中的方法調用與線程狀態關係如下圖:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章