Java併發程式設計基礎
併發與並行
併發與並行的區別?
併發:同時完成多個任務,無需等待當前任務完成即可執行其它任務。例如解決IO密集型任務。
並行:同時在多個CPU中執行多個任務,將任務分為多個部分,在多個CPU中執行多個子任務。用於解決CPU密集型任務。
總之,併發(並行)是為了減少等待(阻塞)時間的技術
舉個例子來說明單執行緒,併發,並行的區別:
小明正在聽歌,她媽媽讓他做作業,這時,小明...
- 先把歌聽完了,再開始寫作業,小明就是單執行緒的
- 暫停歌曲播放,開始寫作業,1分鐘後停止寫作業,開始播放音樂,如此反覆,小明就是併發的
- 一邊聽歌,一邊寫作業,小明就是並行的
什麼時候應該使用併發?
- 永遠不要使用併發
- 除非程式執行的慢到無法忍受的地步
- 除非沒有條件更換具有更好效能的機器的時候
- 除非沒有更好的資料結構和演算法的時候
- 再考慮使用併發
- 不要自己實現,優先考慮使用成熟的併發庫
- 但是,你無法避免併發,必須理解它
多執行緒機制
CPU時間分片機制,CPU將輪流給每個任務分配其佔用的時間。這個過程由執行緒排程器自動控制。
Java併發程式設計的實現
Runnable介面
定義Runnable介面的實現類,作為子執行緒的任務
class ARunnable implements Runnable { @Override public void run() { // TODO } }
建立Thread例項,傳入任務
Thread t=new Thread(new ARunnable());
t.start();
可以使用匿名內部類或者lambda表示式進行簡化
new Thread(()->{
// TODO
}).start();
Thread類
定義Thread的派生類,重寫其run方法
Thread本身就實現了Runnable介面,所以其run方法實際上是Runnable介面中的
class AThread extends Thread { @Override public void run() { // TODO } }
建立Thread例項,啟動執行緒
Thread t = new AThread();
t.start();
與Runnable一樣,也可以進行簡寫
new Thread(()->{
// TODO
}).start();
Callable介面
使用Runnable定義任務時,執行完畢後不能返回任何值。如果需要在任務中返回值,可以使用Callable介面。
定義Callable介面的實現類,重寫call()方法
class ACallable implements Callable<String>
{
@Override
public String call()
{
return "over";
}
}
使用執行器Executor的submit()方法執行任務
ExecutorService executor = Executors.newCachedThreadPool();
ACallable task = new ACallable();
Future<String> future = executor.submit(task);
返回Future型別的物件,使用阻塞的get()方法獲取返回值
try
{
System.out.println(future.get());
}
catch(InterruptedException | ExecutionException e)
{
e.printStackTrace();
}
executor.shutdown();
Fork-Join框架
Java SE7中添加了fork-join框架,專門處理可以分解為子任務的情況,如下所示
if(任務規模小於規定的值)
{
直接處理
}
else
{
分解為子任務;
遞迴處理每一個子任務;
合併結果;
}
使用步驟:
-
定義任務
如果任務有返回值,擴充套件
RecursiveTask
;如果任務沒有返回值,擴充套件RecursiveAction
。它們都是ForkJoinTask
的抽象子類,重寫compute()方法 -
建立
ForkJoinPool
物件 -
呼叫
invoke()
方法執行任務 -
呼叫
join()
方法獲取結果
示例:統計隨機生成的double陣列中,值大於0.5的數字個數
class Counter extends RecursiveTask<Integer>
{
private static final int THRESHOLD=10000;
private double[] nums;
private int from;
private int to;
public Counter(double[] nums,int from,int to)
{
this.nums=nums;
this.from=from;
this.to=to;
}
@Override
protected Integer compute()
{
if(to-from<THRESHOLD) // 直接執行
{
int count=0;
for(int i = from;i < to;i++)
{
if(nums[i]>0.5)
{
count++;
}
}
return count;
}
else
{
//分解為子任務
int mid=(from+to)/2;
Counter first = new Counter(nums,from,mid);
Counter second = new Counter(nums,mid,to);
//遞迴執行子任務
invokeAll(first,second);
//合併結果
return first.join()+ second.join();
}
}
}
ForkJoinPool pool=new ForkJoinPool();
Counter counter=new Counter(nums,0,nums.length);
pool.invoke(counter);
int count = counter.join();
Java執行緒生命週期與管理
執行緒休眠
靜態的sleep()方法會丟擲中斷異常,必須在子執行緒中捕獲處理,因為異常不能跨執行緒丟擲
try
{
//Thread.sleep(1000L);
TimeUnit.MILLISECONDS.sleep(1000L);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
執行緒優先順序
執行緒的優先順序表示該執行緒的重要性,排程器傾向於讓優先順序更高的執行緒優先執行。然而,這種行為是不確定的。因此,不要依賴於優先順序進行任何假設。
Java中執行緒優先順序從低到高分為10個等級(1-10),預設為5
void setPriority(int newPriority)
int getPriority()
執行緒讓步
靜態的yield()方法給執行緒排程器一個暗示,表示當前任務完成的差不多了,可以讓具有相同優先順序的其它執行緒優先執行。
但是,執行緒排程器並不保證按照暗示執行,所以,不要依賴於yield()進行任何假設。
void yield()
後臺執行緒
後臺執行緒也稱守護執行緒,用於在程式執行的過程中,在後臺提供通用的服務。
當所有非後臺執行緒結束時,程式也就終止了,同時自動殺死所有的後臺執行緒。main執行緒不是後臺執行緒。
void setDaemon(boolean on)
boolean isDaemon()
必須線上程啟動之前呼叫setDaemon()方法。
如果是一個後臺執行緒,那麼它建立的任何執行緒都被自動設定為後臺執行緒。
後臺執行緒會在main方法終止後“突然”關閉,即使是finally程式碼快中的程式碼也不一定會被執行。
執行緒的加入
線上程A中呼叫執行緒B的join()方法,執行緒A將被掛起,等到執行緒B執行完畢後,執行緒A再從掛起的地方繼續執行。join()方法可以帶上一個引數,表示掛起的時間。
void join()
void join(long millis)
void join(long millis, int nanos)
join()方法內部使用wait()方法實現。
中斷執行緒
呼叫interrupt()方法可以中斷執行緒。
當在一個被阻塞的執行緒上呼叫interrupt()時,會丟擲異常InterruptedException。
被中斷的執行緒可以決定如何響應中斷,是處理完異常後繼續執行,還是中止執行。
靜態的interrupted()或者非靜態isInterrupted()方法用於檢測當前執行緒的中斷狀態。呼叫前者會清除該執行緒的中斷狀態。如果捕獲了中斷異常,其中斷狀態總是返回false
void interrupt()
boolean isInterrupted()
boolean interrupted()
執行緒狀態(宣告週期)
Java中定義了執行緒的6種狀態,這6種狀態定義在Thread內部列舉類State中
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
- 新建(NEW):當執行緒被new時的狀態,此時它已經分配了必需的系統資源,並執行了初始化。
- 就緒(RUNNABLE):呼叫start()方法後,執行緒準備就緒,只要排程器分配了時間片給它,就可以開始執行任務
- 阻塞(BLOCKED):執行緒嘗試獲取鎖時的狀態
- 等待(WAITING):當執行緒呼叫了非計時的wait()或join()方法後,處於等待狀態
- 計時等待(TIMED_WAITING):當先呼叫了sleep()或計時的wait(),join()方法後,處於計時等待狀態
- 終止(TERMINATED):執行緒執行完畢或者由於未捕獲的異常而終止
可以使用getState()方法獲取執行緒狀態,返回列舉型別的執行緒狀態
State getState()