一、線程的建立
線程的建立能夠經過兩種方式,第一種是 Thread類,第二種是 Runnable接口:
- 繼承 Thread 類,覆蓋 run()
- 實現 Runnable 接口,實現 run()
而後線程的啓用是經過 start() 方法,它會自動調用 run() 方法,以下例:
//繼承Thread
public class MyThread extends Thread {
@Override
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println("MyThread:" + i);
}
}
}
//實現Runnable
public class MyRunnableImpl implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("MyRunnableImpl:" + i);
}
}
}
//測試類
public class Test {
public static void main(String[] args) {
Thread thread1 = new MyThread();
Thread thread2 = new Thread(new MyRunnableImpl());
thread1.start();
thread2.start();
}
}
//輸出結果示例
...
MyThread:23
MyThread:24
MyThread:25
MyRunnableImpl:0
MyThread:26
MyRunnableImpl:1
MyThread:27
MyRunnableImpl:2
...
能夠看到,線程的運行是並行的,而不是先執行完整個 thread1 的 start() 再執行 thread2 的 start()
另外,Runnable 接口的存在主要是爲了解決 Java 中不容許多繼承的問題,因此咱們每每經過實現 Runnable 接口,而後再將其封裝到一個 Thread 類中使用(如上例 Thread thread2 = new Thread(new MyRunnableImpl()); )
事實上,咱們也能夠
經過匿名內部類的方式,快速實現線程,如上能夠修改成:
public class Test {
public static void main(String[] args) {
//Thread
Thread thread1 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("anonymous thread:" + i);
}
}
};
//Runnable
Thread thread2 = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("anonymous runnable:" + i);
}
}
});
thread1.start();
thread2.start();
}
}
二、線程的經常使用方法
2.1 線程的生命週期
要說到線程的經常使用方法,首先就要先了解一個線程的生命週期,以下圖:
- 新建狀態:使用 new 關鍵字和 Thread 類或其子類創建一個線程對象後,該線程對象就處於新建狀態。它保持這個狀態直到程序 start() 這個線程
- 就緒狀態:當線程對象調用了start()方法以後,該線程就進入就緒狀態。就緒狀態的線程處於就緒隊列中(排隊狀態),要等待JVM裏線程調度器的調度
- 運行狀態:若是就緒狀態的線程獲取 CPU 資源,就能夠執行 run(),此時線程便處於運行狀態。處於運行狀態的線程最爲複雜,它能夠變爲阻塞狀態、就緒狀態和死亡狀態
- 阻塞狀態:若是一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所佔用資源以後,該線程就從運行狀態進入阻塞狀態。在睡眠時間已到或得到設備資源後能夠從新進入就緒狀態。阻塞又能夠分爲三種:
- 等待阻塞:運行狀態中的線程執行 wait() 方法,使線程進入到等待阻塞狀態
- 同步阻塞:線程在獲取 synchronized 同步鎖失敗(由於同步鎖被其餘線程佔用)
- 其餘阻塞:經過調用線程的 sleep() 或 join() 發出了 I/O 請求時,線程就會進入到阻塞狀態。當sleep() 狀態超時,join() 等待線程終止或超時,或者 I/O 處理完畢,線程從新轉入就緒狀態
- 死亡狀態:一個運行狀態的線程完成任務或者其餘終止條件發生時,該線程就切換到終止狀態
2.2 線程的經常使用方法
2.2.1 start、run
基於線程的生命週期,那麼 start() 和 run() 方法想必也就不用多說了:
- start() 啓動線程,進入就緒狀態(萬事俱備,只欠CPU)
- run() 運行狀態,執行操做
2.2.2 sleep
sleep() 用於使線程進入阻塞狀態,強制
當前正在執行的線程休眠,計時到後返回到就緒狀態。它是Thread類的靜態方法:
- Thread.sleep(long millis) 休眠 ${millis} 毫秒
- Thread.sleep(long millis, int nanos) 休眠 ${millis} 毫秒 + ${nanos} 納秒
注意:休眠完成以後進入的是就緒狀態,等待進入運行狀態,這意味着 "休眠 --> 執行" 的過程會大於 sleep 方法設置的值,由於實際上還要算上就緒狀態的排隊時間,即真正過程是 "休眠 --> 就緒 --> 執行"
另外,sleep() 是 Thread 的靜態方法,調用的是當前運行的線程,因此
要保證某個線程的休眠,應將 sleep() 的調用放在線程的 run() 之中
2.2.3 yield
Thread.yield() 的做用是,暫停當前正在執行的線程,並讓其回到就緒狀態,以容許具備相同優先級的其餘線程得到運行機會。可是,實際中沒法保證 yield() 達到讓步的目的,由於讓步的線程在就緒狀態仍可能被再次選中執行。
注意:yield() 只是將線程轉到就緒狀態,而不會進入阻塞狀態。
2.2.4 join
join(long millis) 方法的做用是讓某個線程 "霸佔" 資源一段時間,如兩個線程交互運行,若是 thread2.join(5000),那麼 thread2 將強制霸佔執行 5s,以後其餘線程纔有使用的機會。
2.2.5 setPriority
setPriority(int newPriority) 方法用於設置線程的優先級別,數值越高表示優先級越高。優先級的範圍在 1 - 10 之間,默認爲5
每一個線程具備各自的優先級,線程的優先級表示該線程的重要性,若是有不少線程處於就緒狀態,系統會根據優先級來決定優先使哪一個線程進入運行狀態。但這並不意味着低優先級的線程得不到運行,只是運行的概率比較小,如垃圾回收機制線程的優先級就比較低。另外:
- 線程優先級具備繼承性,如A線程啓動B線程,則B線程的優先級和A同樣
- 線程優先級具備隨機性,即線程優先級高的不必定每次都先執行
2.3 守護線程
多線程分類
- 用戶線程:運行在前臺,執行具體的任務,如程序的主線程,用戶本身建立的線程
- 守護線程:運行在後臺,爲其餘前臺線程服務(守護)
它們的區別在於 "
前臺線程保證完成,守護線程不保證完成":
- 前臺線程完成了以後,虛擬機執行的進程纔會結束
- 進程結束以後(此時前臺線程已經結束),也會結束後臺線程,可是不論後臺線程是否已經完成都會將其結束
守護線程的應用如:數據庫鏈接池中的檢測線程、JVM啓動後的檢測線程、垃圾回收線程等
setDaemon(true) 能夠將線程設置爲守護線程,必須在線程進入就緒狀態以前,即必須在 start() 方法調用以前使用。
三、參考連接: