Thread類
學習java線程的開發者,首先遇到的第一個類就是Thread,通過使用Thread類,我們就可以啓動,停止,中斷一個線程. 在同一個時間片裏, 可能會有多個線程在執行, 每個線程都擁有它自己的方法調用堆棧, 參數和變量.每個app至少會有一個線程--主線程(main thread).
創建一個線程
java創建線程有兩種方式
- 創建一個繼承
Thread
的子類,並實現run
方法 - 使用
Thread
的構造方法public Thread(Runnable target)
創建,這個需要傳入一個實現Runnable接口的子類
實現
下面我們分別以這兩種方式實現一下.
- 編寫SubThread繼承Thread,並覆蓋run方法 SubThread.java
public class SubThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i ++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
public static void main(String[] args) {
System.out.println("begin main");
SubThread sub = new SubThread();
sub.start();
System.out.println("end main");
}
}
- 編寫SubRunnable實現Runnable,然後使用構造器Thread(Runnable) 創建一個線程
public class SubRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i ++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
public static void main(String[] args) {
System.out.println("begin main");
Thread thread = new Thread(new SubRunnable());
thread.start();
System.out.println("end main");
}
}
區別
- 使用第一種方法創建的話,你可以在run方法中,可以用
this
直接調用線程的方法,比如獲取線程的id-->this.getId()
- 而使用第二方法創建線程,在
run
中,this
對象壓根就沒有getId()
這個方法,這個時候你只能用Thread.cuurentThread()
這個靜態方法獲取該線程. - 在這裏一般推薦使用第二種方法創建,因爲這樣比較符合面對象的思路,
Thread
就只負責線程的啓動,停止,中斷等操作,而Runnable
就只負責線程要運行某一個具體任務.
不管使用那種方式創建線程,都可以調用
Thread.cuurentThread()
獲取當前的線程
還有,Thread
其實也是Runnable
的一個子類
除了上面兩種創建方法,其中還有另外一種方法創建線程,那就是實現ThreadFactory
接口,這種比較適合批量生成某一種規格的線程
讓線程"睡"一會
調用線程的Thread.sleep()
方法會讓線程睡眠一段時間,這個時候線程會掛起,然後將CPU的時間片轉移給其他線程,讓其他線程獲得執行的機會.
Thread.sleep()
接收一個毫秒值做完參數,並拋出一個InterruptedException
異常.
停止線程
不管是使用哪一種方法創建線程,run
方法的任務執行完了,線程就自動停止.
如果想在中途就停止線程,有下面幾種方式
- 調用線程的
interrupt()
方法,這時線程的中斷位會被標識,並拋出InterruptedException
,例如:
public class StopThread1 {
public static void main(String[] args) throws InterruptedException {
System.out.println("begin main");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i ++){
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + ":" + i);
} catch (InterruptedException e) {
break;
}
}
}
});
thread.start();
System.out.println("main sleep 500ms");
Thread.sleep(500);
thread.interrupt();
System.out.println("end main");
}
}
在調用
thread.interrupt();
這個語句時,會對該線程的中斷狀態標識爲true,然後在拋出InterruptedException
異常時,會清空該中斷位.
修改程序,在拋出InterruptedException
中添加System.out.println("InterruptedException:" + Thread.currentThread().isInterrupted());
,然後再thread.interrupt();
後面添加System.out.println("thread.isInterrupted:" + thread.isInterrupted());
.然後運行程序.這時候運行結果有可能打印出
thread.isInterrupted:true;InterruptedException:false
或者打印出thread.isInterrupted:false;InterruptedException:false
,運行多次結果都有可能不一致,這個是因爲主線程和子線程都通知在執行,還沒有來的及執行主線程的打印語句,子線程異常中的打印語句就已經執行了.
- 可以在線程中加一個
boolean
成員變量,提供setter
方法,然後在run
方法中判斷該變量是否爲true
,若爲true
則停止線程,否則繼續
public class StopThread2 {
public static class StopRunnable implements Runnable{
private boolean isStop = false;
public void setStop(){
this.isStop = true;
}
@Override
public void run() {
int count = 0;
while (!isStop){
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + ":" + count++);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("begin main");
StopRunnable stop = new StopRunnable();
Thread thread = new Thread(stop);
thread.start();
Thread.sleep(200);
stop.setStop();
System.out.println("end main");
}
}
線程的屬性
- id: 通過
Thread.getId()
可以獲取線程的id,線程的id是一個自增長的long, 不能修改 - name: 通過
Thread.getName()
, 用一個字符串來標識線程的名字,可以通過Thread.setName()
或部分構造器修改線程的名字 - priority: 線程的優先級,線程創建默認優先級爲5, 最小爲優先級爲1, 最大爲10.優先級大的線程有機會先執行.但具體那個線程先執行還是要看CPU的心情了.
- state: 線程的狀態, 線程的狀態有以下幾種
Thread.State.NEW
: 新建狀態:這個是線程已經被創建但還沒有調用'start()'方法時的狀態Thread.State.RUNNABLE
: 運行狀態 當前線程已經在JVM中執行Thread.State.BLOCKED
: 阻塞狀態 表示當前線程在等待進入一個同步塊或同步方法,也可以等到一個同步快被提交. 常見的有IO阻塞等.Thread.State.WAITING
: 等待狀態 但線程調用Object.wait()
,Thread.join()
,LockSupport.park()
就會進入等待狀態.當前線程在等待其他線程執行某一個特定操作.比如:當前線程執行Object.wait()
,那麼就需要其他線程執行Object.notify()
或Object.notifyAll()
,如果線程執行了Thread.join()
,則需要等到指定的線程執行結束.Thread.State.TIMED_WAITING
: 有時間的等待 線程在等待某一個等待的時間.比如,線程執行了Thread.sleep
,Object.wait(long)
,Thread.join(long)
等Thread.State.TERMINATED
: 終結 線程已經執行完畢.
- daemon: 這個用來標識線程爲守護線程或非守護線程的,默認創建的線程都是非守護線程.應用程序所有的非守護線程執行完畢之後,則程序就停止運行.比如主線程都是非守護線程,所以主線程會等到主線程的所有語句執行完成,程序纔會停止運行.JVM的資源回收則是一個守護線程.
public class TestDaemonThread {
public static void main(String[] args) {
System.out.println("start main");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i ++){
try {
System.out.println(Thread.currentThread().getName() + ":" + i);
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
thread.start();
System.out.println("end main");
}
}
該例子中,程序必須等到主線程和子線程同時執行完成纔會停止,因爲默認創建的線程都是非守護線程,如果在
thread.start();
前加入thread.setDaemon(true);
, 那麼程序不會等子線程執行完才結束程序的.
Thread.join()
等到某線程執行完畢纔開始執行,如果調用Thread.join(long)
則表示等到某線程執行完畢或指定的超時時間結束後纔開始執行
public class ThreadJoinTest {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
for (int i = 0;i < 10; i ++){
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + ":" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "thread1");
Thread thread2 = new Thread(() -> {
try {
thread1.join();
for (int i = 0;i < 10; i ++){
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + ":" + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread2");
thread1.start();
thread2.start();
}
}
上面的例子,
thread2
線程會等thread1
執行完之後纔開始執行
Thread.yield
這個方法標識當前線程會按時線程調度者讓其他線程先執行.但CPU是否讓其他線程優先執行,還是要看CPU的心情了.
線程的異常
如果線程發現一些運行時異常而沒有在run
方法俘獲,會怎麼辦?
程序會打印出一推錯誤堆棧,如果我們先把線程的錯誤按照某種可讀的方式打印到問題,但又不想在每個
run
方法中增加try{...}catch(Exception e){...}
怎麼辦?
我們查看Thread
類的源碼發現,在Thread
中有一個內部接口UncaughtExceptionHandler
,這個正是我們所需要的.實現這個接口,並調用Thread.setUncaughtExceptionHandler
,那麼但線程出現時,則會回調uncaughtException
方法
public class ThreadExceptionTest {
public static void main(String[] args) throws InterruptedException {
System.out.println("begin main");
Thread thread = new Thread(() -> {
int i = 1 / 0;
},"myThread");
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(String.format("%s發生異常%s", t.getName(), e.getMessage()));
}
});
thread.start();
System.out.println("end main");
}
}