Java多執行緒與執行緒池
關於併發,不要忘了Java中有個十分強大的AQS框架
1.概念
1.1 併發 & 並行 & 序列
併發:巨集觀上多個任務同時執行,實際上同一時刻只有一個執行緒在執行。
並行:微觀上多個任務同時執行,即同一時刻多個任務同時執行。
序列:單執行緒
1.2 併發的作用
併發通常是用於提高執行在單處理器上的程式的效能。乍看不對,多執行緒涉及輪轉使用CPU增加了上下文切換時間,應該更慢。但是我們還需要考慮程式中執行緒阻塞。單執行緒程式執行緒阻塞會導致整個程式阻塞,但是多執行緒可以應對這種情況。也就是說,如果程式中沒有任務會阻塞,多執行緒將毫無意義。
1.3 併發的多面性
優:
讓程式執行更快(總體來看),更充分地利用CPU,提高系統性能。
劣:
帶來執行緒安全問題,增加了系統風險,程式設計難度也提高了。所以在使用併發時,預設執行緒不安全,時刻提醒自己。
1.4 執行緒基本實現機制
底層原理是切分CPU時間,為執行緒分配CPU時間切片,獲得CPU時間切片才可使用CPU。
2.建立執行緒
2.1 定義執行緒任務Runnable
實現Runnable
介面是自定義功能簡單的執行緒的常用方式。
public class RunnableThread implements Runnable {
@Override
public void run() {
// ...
}
}
光是定義一個Runnable
介面的實現類是無法實現執行緒行為的,它就相當於一個普通類,必須要將這個任務顯示地附著到執行緒上
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new RunnableThread());
t.start();
}
}
2.2 Thread類
如果想用Thread的方法,可以繼承Thread建立任務。
public class MyThread extends Thread {
@Override
public void run() {
// ...
}
}
另外還有之中匿名方式,也是lambda。
public class Main {
public static void main(String[] args) {
new Thread(() -> {
System.out.println("是隻豬呀");
}).start();
}
}
2.3 Callable
還可以實現Callable
介面,用得比較少,知道有就行。
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
2.4 執行緒池建立執行緒
關於執行緒池,必須要先理清幾個介面和類的關係,文章後面會介紹使用。
Executor
介面:執行提交的Runnable
任務的物件ExecutorService
介面:提供了更多管理方法API。ExecutorService extends Executor
Executors
類:定義了ExecutorService
、Executor
、ScheduledExecutorService
、ThreadFactory
和Callable
類的工廠和方法。裡面包含了大量的public static ExecutorService
方法,用於建立執行緒池,而這些方法中部分又使用new ThreadPoolExecutor()
來建立執行緒池。ThreadPoolExecutor
:阿里Java開發手冊推薦使用的建立執行緒池的工具。
3.執行緒的生命週期
NEW
:新建RUNNABLE
:執行BLOCKED
:阻塞WAITING
:等待TIMED_WAITING
:計時等待TERMINATED
:已終止
// 新建狀態
Thread t = new MyThread();
// 執行狀態
t.start();
// 阻塞狀態
執行緒 t 需要 synchronized(obj),但是已經被別的執行緒持有,一直去嘗試給這個物件加鎖
// 等待狀態
t.wait();
t.join(); // 等待別的執行緒結束
LockSupport.park();
// 計時等待 給定了時長的
t.sleep(1000);
t.wait(1000);
// 終止
執行緒任務執行完成
如果執行緒需要執行一個長時間任務,可能需要能中斷執行緒
// 中斷執行緒
t.interrupt();
// 檢查執行緒是否中斷 都呼叫native方法
t.isInterrupted();
4.守護執行緒
說到守護執行緒首先想到JVM的垃圾收集器。守護執行緒是為使用者執行緒服務的,所有使用者執行緒結束後,整個程式就結束了,而不會關心守護執行緒狀態。守護執行緒是低優先順序執行緒,任何守護執行緒都是為所有的使用者執行緒服務,而不是某些使用者執行緒。可以t.setDaemon(true);t.start();
這樣設定,要在start()
之前。
5.死鎖
死鎖表現為:多個執行緒都持有部分資源,而又在相互嘗試獲取其它執行緒持有的資源的狀態。
形成鎖的四個必要條件:
- 互斥:兩個執行緒執行都需要相同的資源
- 不可剝奪:執行緒持有的資源不能被其執行緒剝奪,只能自己釋放
- 請求與保持:持有部分資源,同時請求另一部分必要資源
- 迴圈等待:如兩個執行緒都在等待對方釋放資源
死鎖的解決辦法,破壞四個條件中的任意一個:
- 互斥:是必然的,否則沒有加鎖的必要。
- 不可剝奪:將資源定義成原子的,一次性獲取所有需要的資源
- 請求與保持:申請不到資源可以主動釋放已佔有的資源
- 迴圈等待:按順序申請資源,逆序釋放
6.執行緒池的使用
6.1 Executors.newFixedThreadPool
建立固定大小執行緒池
// 5個執行緒的執行緒池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任務
executor.submit(new RunnableTask());
// 關閉執行緒池
executor.shutdown();
// 立即關閉 不會等待執行緒執行結束
executor.shutdownNow();
// 指定延時後關閉 需要捕獲InterruptedException
executor.awaitTermination(60, TimeUnit.SECONDS);
6.2 Executors.newCachedThreadPool
根據任務數量動態調整執行緒池的大小
6.3 ScheduledThreadPool
定時任務
ScheduledExecutorService ses = Executors.newScheduledThreadPool(4);
// 10秒後執行任務 且只執行一次
ses.schedule(new RunnableTask(), 10, TimeUnit.SECONDS);
// 延遲10秒後開始執行任務 任務每5秒內執行一次
ses.scheduleAtFixedRate(new RunnableTask(), 10, 5, TimeUnit.SECONDS);
// 延遲10秒後開始執行任務 任務每隔5秒執行一次(這個是不包含執行任務的時間的,如任務執行需要2秒,那就是每7秒執行一次)
ses.scheduleWithFixedDelay(new RunnableTask(), 10, 5, TimeUnit.SECONDS);
6.4 ThreadPoolExecutor
Executors
的方法也使用了ThreadPoolExecutor
來建立執行緒池。使用ThreadPoolExecutor
能夠讓我們更方便控制其核心引數。關於ThreadPoolExecutor可以參考另一篇部落格。