本文部分摘自《Java 併發編程的藝術》java
現代操做系統在運行一個程序時,會爲其建立一個進程,一個進程裏能夠建立多個線程。現代操做系統調度的最小單元是線程,也叫輕量級進程。這些線程都擁有各自的計數器、堆棧和局部變量等屬性,而且能訪問共享的內存變量。處理器在這些線程上高速切換,讓使用者以爲這些線程在同時執行編程
使用多線程的緣由主要有如下幾點:安全
更多的處理器核心多線程
經過使用多線程技術,將計算邏輯分配到多個處理器核心上,能夠顯著減小程序的處理時間併發
更快的響應時間ide
有時咱們會編寫一些較爲複雜的代碼(主要指業務邏輯),可使用多線程技術,將數據一致性不強的操做派發給其餘線程處理(也可使用消息隊列)。這樣作的好處是響應用戶請求的線程可以儘量快地處理完成,縮短了響應時間this
更好的編程模型操作系統
Java 已經爲多線程編程提供了一套良好的編程模型,開發人員只需根據問題須要創建合適的模型便可線程
現代操做系統基本採用時分的形式調度運行的線程,操做系統會分出一個個時間片,線程分配到若干時間片,當線程的時間片用完了發生線程調度,並等待下次分配。線程分配到的時間片多少也就決定了線程使用處理器資源的多少,而線程優先級就是決定線程須要多或少分配一些處理器資源的線程屬性code
在 Java 線程中,經過一個整型成員變量 priority 來控制優先級,優先級的範圍從 1 ~ 10,在線程構建時能夠經過 setPriority(int) 方法來修改優先級,默認優先級是 5,優先級高的線程分配時間片的數量要多於優先級低的線程。不過,在不一樣的 JVM 以及操做系統上,線程規劃會存在差別,有些操做系統甚至會忽略線程優先級的設定
public class Priority { private static volatile boolean notStart = true; private static volatile boolean notEnd = true; public static void main(String[] args) throws Exception { List<Job> jobs = new ArrayList<Job>(); for (int i = 0; i < 10; i++) { int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY; Job job = new Job(priority); jobs.add(job); Thread thread = new Thread(job, "Thread:" + i); thread.setPriority(priority); thread.start(); } notStart = false; TimeUnit.SECONDS.sleep(10); notEnd = false; for (Job job : jobs) { System.out.println("Job Priority : " + job.priority + ", Count : " + job.jobCount); } } static class Job implements Runnable { private int priority; private long jobCount; public Job(int priority) { this.priority = priority; } @Override public void run() { while (notStart) { Thread.yield(); } while (notEnd) { Thread.yield(); jobCount++; } } } }
運行該示例,在筆者機器上對應的輸出以下
筆者使用的環境爲:Win10 + JDK11,從輸出能夠看到線程優先級起做用了
Java 線程在運行的生命週期中可能處於下表所示的六種不一樣的狀態,在給定的一個時刻,線程只能處於其中的一個狀態
狀態名稱 | 說明 |
---|---|
NEW | 初始狀態,線程被構建,但還沒調用 start() 方法 |
RUNNABLE | 運行狀態,Java 線程將操做系統中的就緒和運行兩種狀態籠統地稱做「運行中」 |
BLOCKED | 阻塞狀態,表示線程阻塞於鎖 |
WAITING | 等待狀態,表示線程進入等待狀態,進入該狀態表示當前線程須要等待其餘線程作出一些特定動做(通知或中斷) |
TIME_WAITING | 超時等待狀態,該狀態不一樣於 WAITING,它是能夠在指定的時間自行返回的 |
TERMINATED | 終止狀態,表示當前線程已經執行完畢 |
線程在自身的生命週期中,並非固定地處於某一狀態,而是隨着代碼的執行在不一樣的狀態之間進行切換
Daemon 線程是一種支持型線程,主要被用做程序中後臺調度以及支持性工做。這意味着,當一個 Java 虛擬機中不存在 Daemon 線程的時候,Java 虛擬機將退出。能夠調用 Thread.setDaemon(true) 將線程設置爲 Daemon 線程
使用 Daemon 線程須要注意兩點:
在運行線程以前首先要構造一個線程對象,線程對象在構造的時候需提供線程需的屬性,如線程所屬的線程組、是不是 Daemon 線程等信息
線程對象在初始化完成以後,調用 start() 方法便可啓動線程
中斷能夠理解爲線程的一個標識位屬性,標識一個運行中的線程是否被其餘線程進行了中斷操做。中斷比如其餘線程對該線程打了個招呼,其餘線程能夠經過調用該線程的 interrupt() 方法對其進行中斷操做
線程經過檢查自身是否被中斷進行響應,線程經過 isInterrupted() 來進行判斷是否被中斷,也能夠調用靜態方法 Tread.interrupted() 對當前線程的中斷標識位進行復位。若是線程已經處於終結狀態,即時線程被中斷過,在調用該對象的 isInterrupted() 時依舊會返回 false
許多聲明拋出 InterruptedException 的方法在拋出異常以前,Java 虛擬機會先將該線程的中斷標識位清除,而後拋出 InterruptedException,此時調用 isInterrupted() 方法將會返回 false
在下面的例子中,首先建立兩個線程 SleepThread 和 BusyThread,前者不停地睡眠,後者一直運行,分別對兩個線程分別進行中斷操做,觀察中斷標識位
public class Interrupted { public static void main(String[] args) throws InterruptedException { // sleepThread 不停的嘗試睡眠 Thread sleepThread = new Thread(new SleepRunner(), "SleepThread"); sleepThread.setDaemon(true); // busyThread 不停的運行 Thread busyThread = new Thread(new BusyRunner(), "BusyThread"); busyThread.setDaemon(true); sleepThread.start(); busyThread.start(); // 休眠 5 秒,讓 sleepThread 和 busyThread 充分運行 TimeUnit.SECONDS.sleep(5); sleepThread.interrupt(); busyThread.interrupt(); System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted()); System.out.println("BusyThread interrupted is " + busyThread.isInterrupted()); // 防止 sleepThread 和 busyThreaad 馬上退出 SleepUtils.second(2); } static class SleepRunner implements Runnable { @Override public void run() { while (true) { SleepUtils.second(10); } } } static class BusyRunner implements Runnable { @Override public void run() { while (true) { } } } }
輸出以下
從結果能夠看出,拋出 InterruptedException 的線程 SleepThread,其中斷標識位被清除了,而一直忙碌運行的線程 BusyThread 的中斷標識位沒有被清除
前面提到的中斷操做是一種簡便的線程間交互方式,適合用來取消或中止任務。除了中斷之外,還能夠利用一個 boolean 變量來控制是否須要中止任務並終止線程
下面的示例中,建立了一個線程 CountThread,它不斷地進行變量累加,而主線程嘗試對其進行中斷操做和中止操做
public class Shutdown { public static void main(String[] args) throws InterruptedException { Runner one = new Runner(); Thread countThread = new Thread(one, "CountThread"); countThread.start(); // 睡眠一秒,main 線程對 CountThread 進行中斷,使 CountThread 可以感知中斷而結束 TimeUnit.SECONDS.sleep(1); countThread.interrupt(); Runner two = new Runner(); countThread = new Thread(two, "CountThread"); countThread.start(); // 睡眠一秒,main 線程對 Runner two 進行中斷,使 CountThread 可以感知 on 爲 false 而結束 TimeUnit.SECONDS.sleep(1); two.cancel(); } private static class Runner implements Runnable { private long i; private volatile boolean on = true; @Override public void run() { while (on && !Thread.currentThread().isInterrupted()) { i++; } System.out.println("Count i = " + i); } public void cancel() { on = false; } } }
main 線程經過中斷操做和 cancel() 方法都可使 CountThread 得以終止。這種經過標識位或者中斷操做的方式可以使線程在終止時有機會去清理資源,而不是武斷地將線程中止,更加安全和優雅