目錄
一、Lock 接口
二、線程間的通信
三、線程池
四、定時器 Timer
五、多線程和異常
一、Lock 接口
1. 線程鎖
- 鎖是控制多個線程對共享資源進行訪問的工具。通常,鎖提供了對共享資源的獨占訪問。一次只能有一個線程獲得鎖,對共享資源的所有訪問都需要首先獲得鎖
//鎖的使用
Lock l = new ReentrantLock(); //創建一個鎖對象
l.lock();
try {// access the resource protected by this lock
} finally {l.unlock();
}
- Lock比Synchronize更為靈活的功能:
boolean tryLock()
僅在調用時鎖為空閑狀態才獲取該鎖。如果鎖可用,則獲取鎖,并立即返回值 true。如果鎖不可用,則此方法將立即返回值 false。
2. 同步弊端
- 影響效率
- 如果出現了嵌套鎖,容易產生死鎖
3. 死鎖
- 死鎖:死鎖是指兩個以上的線程在執行過程中,因為爭奪資源而產生的一種相互等待的現象
二、線程間的通信
1. wait()
- 導致當前線程處于等待狀態
- 只有在其他線程線程中調用了
notify()
方法或notifyAll()
來喚醒,因為wait()
方法阻塞了線程 - 在調用 wait方法之前,當前線程必須擁有此對象監視器(鎖對象),換句話說,必須在 鎖對象 上調用 wait方法(此方法只應該由作為此對象監視器的所有者的線程 來調用)
- 一旦在鎖對象上調用了 wait方法,緊接著:
- 當前線程放棄 cpu 執行權,并等待
- 放棄持有的 鎖對象
wait()
和sleep()
比較
相同點:使當前線程放棄cpu執行權,處于阻塞狀態
不同點:
- 線程因為 sleep() 方法 處于阻塞狀態的時候,不會放棄所持有的鎖對象;線程因為wait() 處于阻塞狀態的時候,會放棄鎖對象
- 使用條件: sleep() 沒有任何特殊條件; 使用 wait() 則必須持有鎖,在鎖對象上調用**wait()**方法
- 喚醒條件:sleep() 的喚醒條件是休眠時間結束;wait() 被喚醒,只能是在其它線程中調用了同一個鎖對象的 notify() 或者 **notifyAll()**方法
2. nofity()
- 喚醒在此對象(調用wait的同一個鎖對象) 監視器上等待的單個線程;如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程
- 直到當前線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程
- 被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程進行競爭
3. notifyAll()
- 喚醒在此對象監視器上等待的所有線程
三、線程池
1. 概述
- 我們創建一個線程,只能使用一次
- 線程池,其實就是一個容納多個線程的容器,其中的線程可以反復使用,省去了頻繁創建線程對象的操作,無需反復創建線程而消耗過多資源
- 線程池的原理:線程池里每一個線程代碼結束后,并不會死亡,而是再次回到線程池中成為空閑狀態,等待下一次被使用
為什么要使用線程池?
- 在 java 中,如果每個請求到達就創建一個新線程,開銷是相當大的。在實際使用中,創建和銷毀線程花費的時間和消耗的系統資源都相當大,甚至可能要比在處理實際的用戶請求的時間和資源要多的多。
- 除了創建和銷毀線程的開銷之外,活動的線程也需要消耗系統資源。如果在一個jvm里創建太多的線程,可能會使系統由于過度消耗內存或“切換過度”而導致系統資源不足。
- 為了防止資源不足,需要采取一些辦法來限制任何給定時刻處理的請求數目,盡可能減少創建和銷毀線程的次數,特別是一些資源耗費比較大的線程的創建和銷毀,盡量利用已有對象來進行服務。
- 線程池主要用來解決線程生命周期開銷問題和資源不足問題。通過對多個任務重復使用線程,線程創建的開銷就被分攤到了多個任務上了,而且由于在請求到達時線程已經存在,所以消除了線程創建所帶來的延遲。這樣,就可以立即為請求服務,使用應用程序響應更快。另外,通過適當的調整線程中的線程數目可以防止出現資源不足的情況。
2. 創建線程池
- 通常,線程池都是通過 線程池工廠 創建,再調用線程池中的方法獲取線程,再通過線程去執行任務方法Executors:線程池創建工廠類
public static ExecutorService newCachedThreadPool()
- 創建一個可根據需要創建新線程 的線程池,但是在以前構造的線程可用時將重用它們 (可變)
- 對于執行很多短期異步(短但是頻繁) 任務的程序而言,這些線程池通常可提高程序性能
- 如果現有線程沒有可用的,則創建一個新線程并添加到池中
- 終止并從緩存中移除那些已有 60 秒鐘未被使用的線程(折中)
public static ExecutorService newFixedThreadPool(int nThreads)
- 創建一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程
- 如果在所有線程處于活動狀態時提交附加任務,則在有可用線程之前,附加任務將在隊列中等待
public static ExecutorService newSingleThreadExecutor()
- 創建一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程
- 可保證順序地執行各個任務
- 以上方法返回了一個
ExecutorService
,該對象表示一個線程池,它可以執行 Runable對象代表的線程。
3. 提交任務
- Runnable 接口
- Callable 接口類似于 Runnable,用來指定線程的任務。其中的 call() 方法,用來返回線程任務執行完畢后的結果,call方法可拋出異常。
ExecutorService
:線程池類<T> Future<T> **submit**(Callable<T> task)
:獲取線程池中的某一個線程對象,并執行線程中的call()方法Future接口
:用來記錄線程任務執行完畢后產生的結果。get()
獲取 Future對象中封裝的數據結果
void shutdown()
:啟動一次順序關閉,執行以前提交的任務,但 不接受新任務shutdownNow()方法
: 試圖停止所有正在執行的活動任務,暫停處理正在等待的任務,并返回等待執行的任務列表。
4. 使用線程池中線程對象的步驟:
- 創建線程池對象
- 創建 Runnable 接口/Callable接口 子類對象
- 提交 Runnable 接口/Callable接口 子類對象
- 關閉線程池
public class ThreadPool {public static void main(String[] args) throws ExecutionException, InterruptedException {//創建一個newCachedThreadPoolExecutorService executorService = Executors.newFixedThreadPool(5);//操作線程池//向線程池提交任務executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello, thread pool");}});//Callable接口的使用Future<Integer> future = executorService.submit(new Callable<Integer>() {@Overridepublic Integer call() throws Exception {TimeUnit.SECONDS.sleep(3);int sum = 0;for (int i = 0; i < 10; i++) {sum += i;}return sum;}});System.out.println(future);System.out.println(future.get());}
}
四、定時器 Timer
1. 概述
- 調度定時任務,幫助我們在稍后的時刻執行定時任務。一種工具,線程用其安排以后在后臺線程中執行的任務。可安排任務 執行一次,或者 定期重復執行
2. TimerTask 定時任務
- 定時任務:
TimerTask
對象表示定時任務 - Timer調度定時任務,定時任務的內容由TimerTask的
run
方法 運行決定 - 在 timer 中所有的定時任務都是運行在同一個線程中
- cancel 取消定時任務
- 如果利用TimerTask的
cancel
要取消定時任務,在定時任務已經開始運行時, 調用cancel方法,是沒有效果的
利用Timer的cancel
方法取消定時任務,其實是終止Timer本身
public class TimerDemo {public static void main(String[] args) {//定義一個定時器// 在timer中所有的定時任務都是運行在同一個線程中Timer timer = new Timer(); //默認不是守護線程//在定時器上調度,定時任務timer.schedule(new MyTimerTask(), 3000);timer.schedule(new MyTimerTask(), 1000);//調度器重復執行定時任務timer.schedule(new MyTimerTask(timer), 0, 2000);}
}class MyTimerTask extends TimerTask {Timer timer;public MyTimerTask(Timer timer) {this.timer = timer;}@Overridepublic void run() {//在輸出之前,取消定時任務//cancel();//利用Timer的cancel方法取消定時任務,其實timer的cance方法,是終止Timer本身timer.cancel();System.out.println("hello timer");}
}
五、多線程和異常
- 在 Thread 類中,不可以拋出編譯型異常,但是可以拋出運行時異常
- 當 運行時異常拋出線程(溢出線程),能不能捕獲?
java語言,并不能直接用try-catch代碼塊捕獲,溢出線程的異常,這是java語言實現上的一個遺憾,但是,并不是說,溢出線程的異常開發者就沒辦法捕獲了。—> Thread.UncaughtExceptionHandler
static setDefaultUncaughtExceptionHandler
,處理所有線程中未捕獲的異常(溢出線程的異常)setUncaughtExceptionHandler
,處理某一個線程中的未捕獲的異常(溢出線程的異常)
public class supplement01 {public static void main(String[] args) {//我們可以通過設置UncaughtExceptionHandler對象,讓該對象捕獲溢出線程的異常// callback 回調Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {@Overridepublic void uncaughtException(Thread t, Throwable e) {// 這個方法,會在日志模塊 -》 將這些異常信息,通過流保存到文件中//處理溢出線程的異常System.out.println("DefaultUncaughtExceptionHandler");}});//在主線程中捕獲異常CatchThreadException1 catchThreadException = new CatchThreadException1();catchThreadException.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {@Overridepublic void uncaughtException(Thread t, Throwable e) {System.out.println("Thread t" + e.getMessage());}});try{//試圖在主線程捕獲子線程拋出的異常catchThreadException.start();}catch (Exception e) {System.out.println("我捕獲到了異常");}}
}class CatchThreadException extends Thread {@Overridepublic void run() {//將異常拋出run方法之外,就等價于將該拋出了線程throw new RuntimeException("測試拋出線程的異常");}
}class CatchThreadException1 extends Thread {@Overridepublic void run() {//將異常拋出run方法之外,就等價于將該拋出了線程throw new RuntimeException("測試拋出線程的異常");}
}
ew RuntimeException("測試拋出線程的異常");}
}class CatchThreadException1 extends Thread {@Overridepublic void run() {//將異常拋出run方法之外,就等價于將該拋出了線程throw new RuntimeException("測試拋出線程的異常");}
}