线程基础知识_Synchronized_ThreadAPI_自定义锁_获取线程运行时异常
Synchronized
synchronized包含monitor enter, monitor exit 2個JVM指令(遵循happens-before原則), 執行monitor exit之前必須存在monitor enter指令.
由于每個對象與一個monitor產生關聯, 線程執行monitor enter時, 就會獲取monitor的lock, monitor中存在計數器, 用于記錄當前lock被獲取的情況, 當線程已經獲取了lock, 再次進行線程重入, lock往上自增.
與之相反, 當線程執行到monitor exit時, 對應的monitor計數器就會自減
- this monitor: synchronized修飾的對象是this
- class monitor: synchronized修飾的對象是class
Thread API
sleep, yield, wait, notify/notifyAll, interrupt, interrupted, join
- sleep
Thread.sleep(), 線程休眠, 但休眠不會釋放鎖資源, 線程從running => block的狀態切換, 可以使用thread.interrupt中斷睡眠 - yield
Thread.yield(), 當前線程放棄CPU資源(CPU資源不緊張, 將忽略), 線程從running => runnable的狀態切換, 因此不能使用interrupt中斷處于runnable狀態的線程 - wait
線程放棄鎖資源, 并進入與鎖關聯的waitSet集合, 狀態變化: Running => Blocked, 可以設定
object.wait()等價于object.wait(0), 表示阻塞時間是永遠
注: wait必須在同步代碼內. waitSet依賴于鎖資源, 沒有在同步代碼內, 拋出IllegalMonitorStateException
wait與sleep相似點:
- notify/notifyAll
notify隨機喚醒waitSet集合中某一線程, notifyAll喚醒waitSet集合中所有的線程, 被喚醒的線程重新爭搶鎖資源, 爭搶到之后, 接著剛才wait的地方往后執行
注: notify必須在同步代碼內. waitSet依賴于鎖資源, 沒有在同步代碼內, 拋出IllegalMonitorStateException - interrupt
打斷當前線程阻塞狀態, 可使用interrupt進行中斷的方法如下:
當線程使用休眠方法進入阻塞狀態后, 使用thread.interrupt設置中斷標志, 隨后線程被中斷, 拋出InterruptedException, 線程終止運行
isInterrupt: 用于判斷線程是否被中斷, 從下面源碼看出, 判斷線程是否被中斷, 不會清除中斷標志
思考: wait, sleep, yield被中斷后會不會清除中斷標志
/*** 測試* @author regotto*/ public class InterruptTest {private static final Object lock = new Object();public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {synchronized (lock) {try {System.out.println("1.isInterrupted: " + Thread.currentThread().isInterrupted());lock.wait();} catch (InterruptedException e) {System.out.println("2.isInterrupted: " + Thread.currentThread().isInterrupted());}}});thread.start();TimeUnit.SECONDS.sleep(1);thread.interrupt();TimeUnit.SECONDS.sleep(1);System.out.println("3.isInterrupted: " + Thread.currentThread().isInterrupted());} }上述結果都為false, 由于isInterrupt不會清除中斷標志, 因此得出結論, 中斷標志是被wait清除的, 同理sleep, yield也是一樣的.
- interrupted: 該方法直接看源碼
從源碼可以看出, 該方法就是調用isInterrupt方法, 但是, 注意傳的參數是true, 意味著, 調用該方法后, 將會清除中斷標志,
- join
A join B, B blocked 直到 A 變為terminal狀態
join源碼如下:
根據源碼得出如下結論:
join內部機制就是wait, 一定記住, 調用wait時, 誰調用, 誰進入blocked, 不要搞混了
測試join狀態下的interrupt
/*** 測試join狀態下的interrupt* @author regotto*/ public class JoinSateExecuteInterrupt {public static void main(String[] args) {Thread main = Thread.currentThread();Thread thread = new Thread(() -> {while (true) {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName() + " interrupt");e.printStackTrace();break;}}});thread.start();//此處提前設置中斷狀態 // main.interrupt();thread.interrupt();try {thread.join();} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName() + " interrupt");e.printStackTrace();} // thread.interrupt(); //此處調用interrupt不能使join方法進入interrupt,因為一直執行thread的run操作,main線程的此行代碼永遠不會執行} }根據Thread API實現自定義鎖
需求分析: 實現synchronized的功能, 保證當線程處于阻塞狀態可被中斷(此處的阻塞代表線程在等待獲取對象monitor), 線程在阻塞狀態下可以計時, 避免線程資源浪費.
詳細設計: 使用wait, notify, 標志變量設計lock, 模擬synchronized同步過程, A線程進入同步代碼塊, 將標志變量變為true, 此時wait有效, B進入線程進入阻塞狀態, 當A線程執行完畢, 調用notifyAll, 喚醒被阻塞的線程, 在此過程中, 進入阻塞狀態下的線程可被中斷.
抽象接口定義:
package com.concurrent.definelock;import java.util.List; import java.util.concurrent.TimeoutException;public interface Lock {void lock() throws InterruptedException;void lock(long mills) throws InterruptedException, TimeoutException;void unlock();/*** 用于獲取處于阻塞狀態的所有線程* @return list<thread>*/List<Thread> getBlockedThreads(); }實現類:
package com.concurrent.definelock;import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeoutException;public class BooleanLock implements Lock {/*** currentThread: 當前擁有鎖的線程* locked: 鎖是被被獲得,默認false未獲得,當調用lock能準確加鎖* blockedList: 存儲哪些線程進入阻塞狀態*/private Thread currentThread;private boolean locked = false;private final List<Thread> blockedList = new ArrayList<>();/*** 第一個線程進入的時候, 標記為加鎖的線程, 剩下的線程進入之后全都wait* 直到該線程unlock,剩下的線程才重新喚醒* @throws InterruptedException*/@Overridepublic void lock() throws InterruptedException {synchronized (this) {while (locked) {if (!blockedList.contains(currentThread)){blockedList.add(currentThread);}System.out.println(Thread.currentThread().getName() + ": 進入wait狀態");//所有線程被喚醒, 只有拿到鎖的那個線程才能接著下去執行, 剩余鎖都只能處于阻塞狀態//wait可中斷方法, 當捕捉到Interrupt信號的時候, 拋出異常, 線程中斷this.wait();System.out.println(Thread.currentThread().getName() + ": 被喚醒");}blockedList.remove(currentThread);System.out.println(Thread.currentThread().getName() + ": 刪除在blockList中值, 設置locked為true");this.locked = true;this.currentThread = Thread.currentThread();}System.out.println(Thread.currentThread().getName() + ": 結束lock方法, 已出synchronize模塊");}/*** 傳入mills <=0 立刻加鎖也可嘗試拋異常, 否則經過mills再加鎖* @param mills* @throws InterruptedException* @throws TimeoutException*/@Overridepublic void lock(long mills) throws InterruptedException, TimeoutException {synchronized (this) {if (0 >= mills) {this.lock();} else {long remainingMills = mills;long endMills = System.currentTimeMillis() + remainingMills;while (locked) {if (0 >= remainingMills){throw new TimeoutException();}if (!blockedList.contains(Thread.currentThread())) {blockedList.add(Thread.currentThread());}this.wait(remainingMills);remainingMills = endMills - System.currentTimeMillis();}blockedList.remove(Thread.currentThread());this.locked = true;this.currentThread = Thread.currentThread();}}}@Overridepublic void unlock() {System.out.println(Thread.currentThread().getName() + ": 進入unlock");synchronized (this) {//不加此步操作,任何線程都將可以執行notifyif (currentThread == Thread.currentThread()) {//修改locked, 表示未加鎖, 喚醒剩余線程System.out.println(Thread.currentThread().getName() + ": 設置locked=false");this.locked = false;this.notifyAll();}}}@Overridepublic List<Thread> getBlockedThreads() {return blockedList;} }測試類:
package com.concurrent.definelock;import java.util.concurrent.TimeUnit; import java.util.stream.IntStream;/*** 測試自定義BooleanLock* @author regotto*/ public class BooleanLockTest {private final Lock lock = new BooleanLock();public void syncMethod() {try {lock.lock();TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public static void main(String[] args) {BooleanLockTest blt = new BooleanLockTest();//定義10個線程, 進行start測試IntStream.range(0, 10).mapToObj(i -> new Thread(blt::syncMethod)).forEach(Thread::start);} }獲取線程運行時異常
線程執行過程中, 可以為特定線程/全局線程設置運行時異常捕獲, 通常使用Thread.setDefaultUncaughtExceptionHandler進行設置.
package com.concurrent.dealthreadexception;import java.util.concurrent.TimeUnit;public class CaptureThreadException {public static void main(String[] args) {//設置線程回調接口, 使得run方法出現異常, 回調Hook能捕獲(該操作類似于監聽者模式)//如果當前線程組沒有設置回調接口, 那么就去父group中查找, 直到找到, 一直找不到//就執行System.errThread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {System.out.println("回調Hook捕獲" + thread.getName() + "的run拋出的異常: " + throwable.getMessage());});final Thread thread = new Thread(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}//run方法不能拋出異常, 就算拋出也無法捕獲, 只能使用特定的Hook獲取異常System.out.println(1 / 0);}, "Test0");thread.start();} }Thread.setDefaultUncaughtExceptionHandler源碼如下:
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {SecurityManager sm = System.getSecurityManager();if (sm != null) {sm.checkPermission(new RuntimePermission("setDefaultUncaughtExceptionHandler"));}//設置默認的異常捕獲處理器, 0defaultUncaughtExceptionHandler = eh;}總結
以上是生活随笔為你收集整理的线程基础知识_Synchronized_ThreadAPI_自定义锁_获取线程运行时异常的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: _一文让你透彻理解Linux的SOCKE
- 下一篇: 数值字符串