1. 線程狀態轉換
新建(New)
創建後尚未啓動
可運行(Runnable)
可能正在運行,也可能正在等待CPU時間片。
包含了操作系統線程狀態中的Running和Ready。
阻塞(Blocked)
等待獲取一個排它鎖,如果其線程釋放了鎖就會結束次狀態。
無限期等待(Waiting)
等待其它線程顯式地喚醒,否則不會被分配 CPU 時間片。
限期等待(Timed Waiting)
無需等待其他線程顯示的被喚醒,在一定時間之後會被系統自動喚醒。
調用Thread.sleep()方法使線程進入限期等待狀態時,常常用 是一個線程睡眠 進行描述。
調用Object.wait()方法使線程進入限期等待或者無限期等待時,嚐嚐用掛起一個線程進行描述。
睡眠和掛起是用來描述行爲,而阻塞和等待用來描述狀態。
阻塞和等待的區別在於,阻塞是被動地,它是在等待獲取一個排他鎖。而等待時主動地,通過調用Thread.sleep()和Object.wait()等方法進去。
死亡(Terminated)
可以使線程結束之後自己結束,或者產生了異常而結束。
2. 使用線程
有三種使用線程方法:
- 實現Runnable接口;
- 實現Callable接口;
- 繼承Thread類。
實現Runnable和Callable接口的類只能當做一個可以在線程中運行的任務,不是真正意義上的線程,因此最後還是需要通過Thread來調用。可以說任務是通過線程驅動從而執行的。
實現Runnable接口
需要實現run()方法。
通過Thread調用start()方法來啓動線程。
public class MyRunnable implements Runnable {
public void run() {
// ...
}
}
public static void main(String[] args) {
MyRunnable instance = new MyRunnable();
Thread thread = new Thread(instance);
thread.start();
}
實現Callable接口
與Runnable相比,Callable可以有返回值,返回值通過FutureTask進行封裝。
public class MyCallable implements Callable<Integer> {
public Integer call() {
return 123;
}
}
public static void main(String[] args) throws ExecutionException,
InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
繼承Thread類
同樣也是需要實現run()方法,因爲Thread類也實現了Runnable接口。
當調用start()方法啓動一個線程時,虛擬機會將該線程放入就緒隊列中等待被調度,當一個線程被調度時會執行該線程的run()方法。
public class MyThread extends Thread {
public void run() {
// ...
}
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
實現接口 VS 繼承Thread
實現接口會更好一些,因爲:
- Java不支持多繼承,因此繼承了Thread類就無法繼承其他類,但是可以實現多個接口;
- 類可能只要求可執行就行,繼承整個Thread類開銷過大。
3.基礎線程機制
Executor
Executor管理多個異步任務的執行,而無需程序員顯示的管理線程的生命週期。這裏的異步是指多個任務的執行互不干擾,不需要進行同步操作。
主要有三種Executor:
- CachedThreadPool:一個任務創建一個下次呢很難過;
- FixedThreadPool:所有任務只能使用固定大小的線程;
- SingleThreadExecutor:相當於大小爲1的FixedThreadPool。
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executorService.execute(new MyRunnable());
}
executorService.shutdown();
}
Daemon
守護線程是程序運行時在後臺提供服務的線程,不屬於程序中不可或缺的部分。
當所有非守護線程結束時,程序也就終止,同時會殺掉所有守護線程。
main()屬於非守護線程。
使用setDaemon()方法將一個線程設置爲守護線程。
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.setDaemon(true);
}
sleep()
Thread.sleet(millisec) 方法會休眠當前正在執行的線程,millisec單位爲毫秒。
sleet()可能會拋出InterruptedException,因爲一場不能跨線程傳播回main()中,因此必須在本地進行處理。線程中拋出的其他異常也同樣需要在本地進行處理。
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
yield()
對靜態方法Thread.yield()的調用聲明瞭當前線程已經完成了生命週期中最重要的部分,可以切換給其他線程來執行。該方法只是對線程調度器的一個建議,而且也只是建議具有相同優先級的其他線程可以運行。
public void run() {
Thread.yield();
}
4.中斷
一個線程執行完畢之後會自動結束,如果在運行過程中發生異常也會提前結束。
InterruptedException
通過調用一個線程的interrupt()來中斷該線程,如果該線程處於阻塞、限期等待或者無限期等待狀態,那麼就會拋出InterruptedException,從而提前結束該線程。但是不能中斷I/O阻塞和synchronized鎖阻塞。
對於以下代碼,在main()中啓動一個線程之後再中斷它,由於線程中調用了Thread.sleep()方法,因此會拋出一個InterruptedException,從而提前結束線程,不再執行之後的語句。
public class InterruptExample {
private static class MyThread1 extends Thread {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("Thread run");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new MyThread1();
thread1.start();
thread1.interrupt();
System.out.println("Main run");
}
Main run
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at InterruptExample.lambda$main$0(InterruptExample.java:5)
at InterruptExample$$Lambda$1/713338599.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
interrupted()
如果一個線程的run()方法執行了一個無限循環,並且沒有執行sleep()等會拋出InterruptedException的操作,那麼調用線程的interrupt()方法就無法使線程提前結束。
但是調用interrupt()方法會設置線程中斷標記,此時調用interrupted()方法會返回true。因此可以在循環體中使用interrupted()方法來判斷線程是否處於中斷狀態,從而提前結束線程。
public class InterruptExample {
private static class MyThread2 extends Thread {
@Override
public void run() {
while (!interrupted()) {
// ..
}
}
System.out.println("Thread end");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread2 = new MyThread2();
thread2.start();
thread2.interrupt();
}
Thread end
Executor 的中斷操作
調用Executor的shutdown()方法會等待線程都執行完畢之後再關閉,但是如果調用的是shutdownNow()方法,則相當於調用每個線程的interrupt()方法。
以下使用Lambda創建線程,相當於創建了一個匿名內部線程。
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
try {
Thread.sleep(2000);
System.out.println("Thread run");
} catch (InterruptedException e) {
e.printStackTrace();
} });
executorService.shutdownNow();
System.out.println("Main run");
}
Main run
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at ExecutorInterruptExample.lambda$main$0(ExecutorInterruptExample.java:9)
at ExecutorInterruptExample$$Lambda$1/1160460865.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
如果只想中斷 Executor 中的一個線程,可以通過使用 submit() 方法來提交一個線程,它會返回一個 Future<?> 對 象,通過調用該對象的 cancel(true) 方法就可以中斷線程。
5.互斥同步
Java提供了兩種鎖機制來控制多個線程對共享資源的互斥訪問,第一個是JVM實現的synchronized,另一個是JDK實現的ReentrantLock。
synchronized
- 同步一個代碼塊
public void func() {
synchronized (this) {
// ...
}
}
它只作用於同一個對象,如果調用兩個對象上的同步代碼塊,就不會進行同步。
對於以下代碼,使用ExecutorService執行了兩個線程,由於調用的是同一個對象的同步代碼塊,因此這兩個線程會進行同步,當一個線程進入同步語塊時,另一個線程就必須等待。
public class SynchronizedExample {
public void func1() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
}
}
}
public static void main(String[] args) {
SynchronizedExample e1 = new SynchronizedExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> e1.func1());
executorService.execute(() -> e1.func1());
}
01234567890123456789
對於以下代碼,兩個線程調用了不同對象的同步代碼塊,因此這兩個線程不需要同步。從輸出結果可以看出兩個線程交叉執行。
public static void main(String[] args) {
SynchronizedExample e1 = new SynchronizedExample();
SynchronizedExample e2 = new SynchronizedExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> e1.func1());
executorService.execute(() -> e2.func1());
}
00112233445566778899
- 同步一個方法
public synchronized void func () {
// ...
}
他和同步代碼塊一樣,作用於同一個對象。
3. 同步一個類
public void func() {
synchronized (SynchronizedExample.class) {
// ...
}
}
作用於整個類,也就是說兩個線程調用同一個類的不同對象上的這種同步語句,也會進行同步。
public class SynchronizedExample {
public void func2() {
synchronized (SynchronizedExample.class) {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
}
}
}
public static void main(String[] args) {
SynchronizedExample e1 = new SynchronizedExample();
SynchronizedExample e2 = new SynchronizedExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> e1.func2());
executorService.execute(() -> e2.func2());
}
01234567890123456789
- 同步一個靜態方法
public synchronized static void fun() {
// ...
}
作用於整個類。
ReentrantLock
ReentrantLock是java.util.concurrent(J.U.C)包中的鎖。
public class LockExample {
private Lock lock = new ReentrantLock();
public void func() {
lock.lock();
try {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
} finally {
lock.unlock(); // 確保釋放鎖,從而避免發生死鎖。
}
}
}
public static void main(String[] args) {
LockExample lockExample = new LockExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> lockExample.func());
executorService.execute(() -> lockExample.func());
}
01234567890123456789
比較
- 鎖的實現
synchronized是jvm實現的,而ReentrantLock是JDK實現的。 - 性能
新版本Java對synchronized進行了很多優化,例如自旋鎖等,synchronized與ReentrantLock大致相同。 - 等待可中斷
當持有鎖的線程長期不釋放鎖的時候,正在等待的線程可以選擇放棄等待,改爲處理其他事情。
ReentrantLock可中斷,synchronized不行。 - 公平鎖
公平鎖是指多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖。
synchronized中的鎖時非公平的,ReentrantLock默認情況下是非公平的,但是也可以是公平的。 - 鎖綁定多個條件
一個ReentrantLock可以同時綁定多個Condition對象。
使用選擇
除非需要使用ReentrantLock的高級功能,否則優先使用synchronized,因爲這是synchronized是JVM實現的一種鎖機制,JVM原生的支持他,而ReentrantLock不是所有的JDK版本都支持,並且使用Synchronized不用擔心沒有釋放鎖而導致死鎖的問題,因爲JVM會確保鎖的釋放。
6.線程之間的協作
當多個線程可以一起工作去解決某個問題時,如果某部分必須在其他部分之前完成,那麼就需要對線程進行協調。
join()
在線程中調用另一個線程的join()方法,會將當前線程掛起,而不是忙等待,直到目標線程結束。
對於以下代碼,雖然b線程先啓動,但是還是因爲在b線程中調用了a線程的join()方法,b線程會等待a線程結束才繼續執行,因此最後能夠保證a線程的輸出先於b線程的輸出。