本篇文章會從一下幾個方面來說明Java多線程的基本用法:
- 如何使用多線程
- 如何得到多線程的一些信息
- 如何停止線程
- 如何暫停線程
- 線程的一些其他用法
如何使用多線程
啟動線程的兩種方式
Java 提供了2種方式來使用多線程, 一種是編寫一個類來繼承Thread,然后覆寫run方法,然后調用start方法來啟動線程。這時這個類就會以另一個線程的方式來運行run方法里面的代碼。另一種是編寫一個類來實現Runnable接口,然后實現接口方法run,然后創造一個Thread對象,把實現了Runnable接口的類當做構造參數,傳入Thread對象,最后該Thread對象調用start方法。
這里的start方法是一個有啟動功能的方法,該方法內部回調run方法。所以,只有調用了start方法才會啟動另一個線程,直接調用run方法,還是在同一個線程中執行run,而不是在另一個線程執行run
此外,start方法只是告訴虛擬機,該線程可以啟動了,也就說該線程在就緒的狀態,但不代表調用start就立即運行了,這要等待JVM來決定什么時候執行這個線程。也就是說,如果有兩個線程A,B ,A先調用start,B后調用start,不代表A線程先運行,B線程后運行。這都是由JVM決定了,可以認為是隨機啟動。
下面我們用實際的代碼,來說明兩種啟動線程的方式:
第一種,繼承Thread
1
2
3
4
5
6
7
|
public class ExampleThread extends Thread{ @Override public void run() { super .run(); System.out.println( "這是一個繼承自Thread的ExampleThread" ); } } |
測試的代碼可以看test目錄下的ExampleThreadTest類
另一種,實現了Runnable接口
1
2
3
4
5
|
public class ExampleRunable implements Runnable{ public void run() { System.out.println( "這是實現Runnable接口的類" ); } } |
測試的代碼可以看test目錄下的ExampleRunableTest類。
如何得到多線程的一些信息
我們在啟動多線程之后,希望能通過一些API得到啟動的線程的一些信息。JDK給我們提供了一個Thread類的方法來得到線程的一些信息。
- 線程的名字 —— getName()
- 線程的ID —— getId()
- 線程是否存活 —— isAlive()
得到線程的名字
這些方法是屬于Thread的內部方法,所以我們可以用兩種方式調用這些方法,一個是我們的類繼承Thread來使用多線程的時候,可以用過this來調用。另一種是通過Thread.currentThread() 來調用這些方法。但是這兩個方法在不同的使用場景下是有區別的。
我們先簡單來看兩個方法的使用。
第一個Thread.currentThread()的使用,代碼如下:
1
2
3
4
5
6
7
8
9
10
|
public class ExampleCurrentThread extends Thread{ public ExampleCurrentThread(){ System.out.println( "構造方法的打印:" + Thread.currentThread().getName()); } @Override public void run() { super .run(); System.out.println( "run方法的打印:" + Thread.currentThread().getName()); } } |
測試的代碼如下:
1
2
3
4
5
6
7
8
9
10
|
public class ExampleCurrentThreadTest extends TestCase { public void testInit() throws Exception{ ExampleCurrentThread thread = new ExampleCurrentThread(); } public void testRun() throws Exception { ExampleCurrentThread thread = new ExampleCurrentThread(); thread.start(); Thread.sleep( 1000 ); } } |
結果如下:
1
2
3
|
構造方法的打印:main run方法的打印:Thread-0 構造方法的打印:main |
為什么我們在ExampleCurrentThread內部用Thread.currentThread()會顯示構造方法的打印是main,是因為Thread.currentThread()返回的是代碼段正在被那個線程調用的信息。這里面很顯然構造方法是被main線程執行的,而run方法是被我們自己啟動的線程執行的,因為沒有給他起名字,所以默認是Thread-0。
接下來,我們在看一看繼承自Thread,用this調用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class ComplexCurrentThread extends Thread{ public ComplexCurrentThread() { System.out.println( "begin=========" ); System.out.println( "Thread.currentThread().getName=" + Thread.currentThread().getName()); System.out.println( "this.getName()=" + this .getName()); System.out.println( "end===========" ); } @Override public void run() { super .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==========" ); } } |
測試代碼如下:
1
2
3
4
5
6
7
8
|
public class ComplexCurrentThreadTest extends TestCase { public void testRun() throws Exception { ComplexCurrentThread thread = new ComplexCurrentThread(); thread.setName( "byhieg" ); thread.start(); Thread.sleep( 3000 ); } } |
結果如下:
1
2
3
4
5
6
7
8
|
begin========= Thread.currentThread().getName=main this.getName()=Thread-0 end=========== run begin======= Thread.currentThread().getName=byhieg this.getName()=byhieg run end========== |
首先在創建對象的時候,構造器還是被main線程所執行,所以Thread.currentThread()得到的就是Main線程的名字,但是this方法指的是調用方法的那個對象,也就是ComplexCurrentThread的線程信息,還沒有setName,所以是默認的名字。然后run方法無論是Thread.currentThread()還是this返回的都是設置了byhieg名字的線程信息。
所以Thread.currentThread指的是具體執行這個代碼塊的線程信息。構造器是main執行的,而run方法則是哪個線程start,哪個線程執行run。這么看來,this能得到的信息是不準確的,因為如果我們在run中執行了this.getName(),但是run方法卻是由另一個線程start的,我們是無法通過this.getName得到運行run方法的新城的信息的。而且只有繼承了Thread的類才能有getName等方法,這對于Java沒有多繼承的特性語言來說,是個災難。所有后面凡是要得到線程的信息,我們都用Thread.currentThread()來調用API。
得到線程的ID
調用getID取得線程的唯一標識。這個和上面的getName用法一致,沒什么好說的,可以直接看ExampleIdThread和他的測試類ExampleIdThreadTest。
判斷線程是否存活
方法isAlive()的作用是測試線程是否處于活動狀態。所謂活動狀態,就是線程已經啟動但是沒有終止。即該線程start之后,被認為是存活的。
我們看一下具體的例子:
1
2
3
4
5
6
7
|
public class AliveThread extends Thread{ @Override public void run() { super .run(); System.out.println( "run方法中是否存活" + " " + Thread.currentThread().isAlive()); } } |
測試方法如下:
1
2
3
4
5
6
7
8
9
10
|
public class AliveThreadTest extends TestCase { public void testRun() throws Exception { AliveThread thread = new AliveThread(); System.out.println( "begin == " + thread.isAlive()); thread.start(); Thread.sleep( 1000 ); System.out.println( "end ==" + thread.isAlive()); Thread.sleep( 3000 ); } } |
結果如下:
1
2
3
|
begin == false run方法中是否存活 true end ==false |
我們可以發現在start之前,該線程被認為是沒有存活,然后run的時候,是存活的,等run方法執行完,又被認為是不存活的。
如何停止線程
判斷線程是否終止
JDK提供了一些方法來判斷線程是否終止 —— isInterrupted()和interrupted()
停止線程的方式
這個是得到線程信息中比較重要的一個方法了,因為這個和終止線程的方法相關聯。先說一下終止線程的幾種方式:
- 等待run方法執行完
- 線程對象調用stop()
- 線程對象調用interrupt(),在該線程的run方法中判斷是否終止,拋出一個終止異常終止。
- 線程對象調用interrupt(),在該線程的run方法中判斷是否終止,以return語句結束。
第一種就不說了,第二種stop()方法已經廢棄了,因為可能會產生如下原因:
- 強制結束線程,該線程應該做的清理工作,無法完成。
- 強制結束線程,該線程已操作的加鎖對象強制解鎖,造成數據不一致。
具體的例子可以看StopLockThread以及他的測試類StopLockThreadTest
第三種,是目前推薦的終止方法,調用interrupt,然后在run方法中判斷是否終止。判斷終止的方式有兩種,一種是Thread類的靜態方法interrupted(),另一種是Thread的成員方法isInterrupted()。這兩個方法是有所區別的,第一個方法是會自動重置狀態的,如果連續兩次調用interrupted(),第一次如果是false,第二次一定是true。而isInterrupted()是不會的。
例子如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class ExampleInterruptThread extends Thread{ @Override public void run() { super .run(); try { for ( int i = 0 ; i < 50000000 ; i++){ if (interrupted()){ System.out.println( "已經是停止狀態,我要退出了" ); throw new InterruptedException( "停止......." ); } System.out.println( "i=" + (i + 1 )); } } catch (InterruptedException e){ System.out.println( "順利停止" ); } } } |
測試的代碼如下:
1
2
3
4
5
6
7
8
|
public class ExampleInterruptThreadTest extends TestCase { public void testRun() throws Exception { ExampleInterruptThread thread = new ExampleInterruptThread(); thread.start(); Thread.sleep( 1000 ); thread.interrupt(); } } |
第四種方法和第三種一樣,唯一的區別就是將上面的代碼中的拋出異常換成return,個人還是喜歡拋出異常,這里處理的形式就比較多,比如打印信息,處理資源關閉或者捕捉之后再重新向上層拋出。
注意一點,我們上面拋出的異常是InterruptedException,這里簡單說一下可能產生這個異常的原因,在原有線程sleep的情況下,調用interrupt終止線程,或者先終止線程,再讓線程sleep。
如何暫停線程
在JDK中提供了以下兩個方法用來暫停線程和恢復線程。
- suspend()——暫停線程
- resume()——恢復線程
這兩個方法和stop方法一樣是被廢棄的方法,其用法和stop一樣,暴力的暫停線程和恢復線程。這兩個方法之所以是廢棄的主要由以下兩個原因:
- 線程持有鎖定的公共資源的情況下,一旦被暫停,則公共資源無法被其他線程所持有。
- 線程強制暫停,導致該線程執行的操作沒有執行完全,這時訪問該線程的數據會出現數據不一致。
線程的一些其他用法
線程的其他的一些基礎用法如下:
- 線程讓步
- 設置線程的優先級
- 守護線程
線程讓步
JDK提供yield()方法來讓線程放棄當前的CPU資源,將它讓給其他的任務去占用CPU時間,但是這也是隨機的事情,有可能剛放棄資源,又馬上占用時間片了。
具體的例子可以參考ExampleYieldThread以及他的測試類ExampleYieldThreadTest
設置線程的優先級
我們可以設置線程的優先級來讓CPU盡可能的將執行的資源給優先級高的線程。Java設置了1-10這10個優先級,又有三個靜態變量來提供三個優先級:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/** * 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 ; |
我們可以通過setPriority來設置線程的優先級,可以直接傳入上訴三個靜態變量,也可以直接傳入1-10的數字。設置后線程就會有不同的優先級。如果我們不設置優先級,會是什么情況?
線程的優先級是有繼承的特性,如果我們在A線程中啟動了B線程,則AB具有相同的優先級。一般我們在main線程中啟動線程,就和main線程有一致的優先級。main線程的優先級默認是5。
下面說一下優先級的一些規則:
- 優先級高的線程一般會比優先級低的線程獲得更多的CPU資源,但是不代表優先級高的任務一定先于優先級低的任務先執行完。因為不同優先級的線程中run方法內容可能不一樣。
- 優先級高的線程一定會比優先級低的線程執行的快。如果兩個線程是一樣的run方法,但是優先級不一樣,確實優先級高的線程先執行完。
線程守護
JDK中提供setDaemon的方法來設置一個線程變成守護線程。守護線程的特點是其他非守護線程執行完,守護線程就自動銷毀,典型的例子是GC回收器。
具體可以看ExampleDaemonThread和ExampleDaemonThreadTest。
總結
這篇文章主要總結了Java線程的一些基本的用法,關于線程安全,同步的知識,放到了第二篇。
以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持服務器之家!
原文鏈接:http://www.cnblogs.com/qifengshi/p/6230322.html