《瘋狂的JAVA講義》筆記-第16章多線程
- 三種方法實現多線程
實現方法 | 特點 | 例子 |
---|---|---|
繼承 Thread |
無法共享變量,以run 爲線程執行體 |
new Thread().start |
實現 Runnable 接口,並作爲 Thread 的target 運行 |
共享實現類變量,以run 爲線程執行體 |
new Thread(new MyRun()).start |
實現Callable 接口,並使用FutureTask 包裝,把包裝對象作爲Thread 的target運行 |
可以拋異常且有返回值(task.get() ),共享變量,以call 爲線程執行體 |
task = new FutureTask(new MyCall()); Thread thread = new Thread(task); |
-
sleep
、yield
以及join
sleep
:讓線程睡眠一段時間,處於 阻塞狀態。join
:讓線程等待另一個線程執行結束,可用於流程性的控制yield
: 線程讓步,不阻塞線程,只是將線程設爲 就緒狀態,將CPU讓給優先級更高的線程。可能會出現沒有其他優先級更高的線程,從而剛成爲就緒狀態後又被操作系統調度成爲 運行 狀態的線程
-
後臺線程/守護線程
- 爲其他線程服務的,一直在後臺運行的線程,可以通過
thread.setDaemon(true)
設置,但是須在運行前進行設置,且中途不能修改。當所有的前臺程序結束,後臺線程自動結束。例子:JVM的GC
- 爲其他線程服務的,一直在後臺運行的線程,可以通過
-
線程同步
- 同步代碼塊:同步監視器(互斥量)爲
obj
,只有獲取到該監視器纔可以進入代碼塊
synchronized(obj){ .... }
- 同步方法:同步監視器爲
this
,只有獲取到account
實例纔可以進入代碼塊
//Account.java public synchronized int method(){ .... }
- 靈活的加鎖同步Lock :較爲常用的是可重入鎖
ReetrantLock
,synchronized
也是可重入鎖
private final lock = new ReentrantLock(); public int method(){ lock.lock(); try{ ... }finally{ lock.unlock(); } }
- 同步代碼塊:同步監視器(互斥量)爲
-
可重入鎖與不可重入鎖
- 可重入鎖:當一個進程獲取到 A 鎖,如果在遞歸或者其他需要 A 鎖的地方時,無需再次獲取 A 鎖。
- 不可重入鎖:需要再次獲取 A 鎖,容易導致死鎖
public synchronized int method1(){ method2(); } public synchronized int method2(){ ..... } //這種情況下 可重入不會死鎖,但 不可重入則會死鎖
-
鎖的釋放
- 釋放鎖的情景
- 方法執行結束
- 同步代碼塊或同步方法執行了 break 或 return 語句
- 出現異常,方法結束
- 程序執行了
wait()
- 不會釋放鎖的情景
- 調用
sleep()
或yield()
方法 - 其他線程調用其他線程的
suspend()
方法
-
進程通信:當使用 同步方法時,同步監視器是
this
,即對象本身,可以使用Object
類的wait``notify``notifyAll
方法進行通信wait()
:讓當前進程暫停,直到其他線程調用 該同步監視器 的notify
或notifyAll
方法才被喚醒,也可以 等指定秒數後自行喚醒notify()
:喚醒 在同一個同步監視器上等待的單個線程。如果有多個,則選擇是任意性的notifyAll()
:喚醒所有在同一個同步監視器上等待的線程
如果不使用 同步方法,則不存在
wait()
等方法,可以使用Condition
來進行類似的操作await()
:跟wait()
相似,讓當前線程暫停,並釋放已經獲得的Lock
對象signal()/signalAll()
:與notify()/notifyAll()
一樣
-
使用 阻塞隊列 控制通信:消費者/生產者模型
- 使用
take()
put()
可以使進程阻塞
- 使用
-
線程池
- 由於頻繁創建銷燬非常影響性能,所以在
JAVA 5
後有了線程池,可使用工廠類Executors
的以下方法產生線程池 newCachedThreadPool()
:根據需要創建線程,並將線程緩存在線程池裏newFixedThreadPool()
:創建固定長度,可複用的線程池newSingleThreadPool()
: 創建只有一個線程的線程池newScheduledThreadPool(int corePoolSize)
:創建指定線程線程數的線程池,可以在指定延遲後執行線程任務
以上方法返回一個
ExecutorService
對象,有以下方法Future<?> submit(Runnable task)
:由於run
方法沒有返回值,所以返回值爲null
,可以調用Future
對象的isDone
/isCancelled
方法獲取執行狀態<T> Future<T> submit(Runnable task,T result)
:線程池在空閒時會執行 task 任務,在結束後返回 result<T> Future<T> submit(Callable<T> task)
:Future
對象是call()
方法的返回值
當不想提交任務後,請使用
shutdown()
方法關閉線程池,但是線程池中線程是在執行完當前任務後纔會死亡,如果需要立即死亡,使用shutdownNow()
方法,可以視圖停止所有正在執行的任務 - 由於頻繁創建銷燬非常影響性能,所以在
-
ForkJoinPool
- 單核上多線程只能算 併發,但是多核上就可以實現 並行(parallelism),
ForkJoinPool
線程就是實現這個的,他是ExecutorService
的實現類 ForkJoinPool()
:根據Runtime.availableProcessors()
方法指定 並行線程數,當然也可以傳入int parallelism
指定並行線程數- 沒有返回值的任務需要實現
RecursiveAction
,有返回值的任務則實現RecursiveTask<T>
,最後使用submit
方法提交即可
- 單核上多線程只能算 併發,但是多核上就可以實現 並行(parallelism),
-
TreadLocal
類可以實現每個線程都使用自己的變量副本,即每個線程的該變量都是獨立的,不會發生死鎖、等待鎖的情況。使用方法private ThreadLocal<String> name = new ThreadLocal<>();