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類 中的方法調用與線程狀態關係如下圖: