同步控制和鎖,ReenterLock和Condition的詳細使用
文章目錄
多執行緒團隊合作:同步控制和鎖
同步控制是併發程式必不可少的重要手段。synchronized關鍵字就是一種最簡單的控制方法。同時,wait()和notify()方法起到了執行緒等待和通知的作用。這些工具對於實現複雜的多執行緒協作起到了重要的作用。接下來將介紹synchronized,wait,notify方法的代替品(或者說是增強版)——重入鎖,這個專題需要大家對多執行緒基本的內部鎖synchronized,wait, notify方法先有基本的認識。
1 synchronized的功能擴充套件: 重入鎖
重入鎖完全可以代替synchronized關鍵字。在早期JDK版本,重入鎖的效能遠遠優於synchronized關鍵字,在JDK後期版本,對synchronized關鍵字做了大量的優化,使得兩者的效能差不多。
下面展示一段簡單的重入鎖ReentrantLock使用案例:
public class ReenterLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static int i=0;
public void run() {
for(int j=0;j<10000000;j++){
lock.lock();
// lock.lock();
try {
i++;
}finally {
lock.unlock();
// lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLock r1 = new ReenterLock();
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r1);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
上述程式碼建立了一個全域性的ReentrantLock物件,這個物件就是重入鎖物件,該物件的lock()和unlock()方法之間7~12行的程式碼區域就是重入鎖的保護臨界區,確保了多執行緒對i變數的操作安全性。
從這段程式碼可以看到,與synchronized相比,重入鎖有著顯示操作的過程。開發人員必須手動指定何時加鎖 ,何時釋放鎖。也正是因為這樣,重入鎖邏輯控制遠遠要好於synchronized。但值得注意的是,在退出臨界區時,必須記得要釋放鎖,否者永遠沒有機會再訪問臨界區了,會造成其執行緒的飢餓甚至是死鎖。
重入鎖之所以被稱作重入鎖是因為重入鎖是可以反覆進入的。當然,這裡的反覆進入僅僅侷限於一個執行緒。上訴程式碼還可以這樣寫:
public void run() {
for(int j=0;j<10000000;j++){
lock.lock();
lock.lock();
try {
i++;
}finally {
lock.unlock();
lock.unlock();
}
}
}
在這種情況下,一個執行緒連續兩次獲得同一把鎖。這是允許的!但要注意的是,如果一個執行緒多次獲得鎖,那麼在釋放鎖的時候,也必須釋放相同次數。如果釋放的次數多了,那麼會得到一個java.lang.IllegalMonitorStateException異常,反之,如果釋放所得次數少了,MAME相當於縣城還持有這個鎖,因此,其他執行緒也無法進入臨界區
處使用上的靈活性以外,重入所還提供了一些高階功能。比如重入鎖提供的中斷處理的能力
1.1 中斷響應
重入鎖除了提供上述的基本功能外,還提供了一些高階功能。比如,重入鎖可以提供中斷處理的能力。這是一個非常重要的功能,synchronized是沒有中斷功能的。在等待鎖的過程中,程式可以根據需要取消對鎖的請求。這是synchronized辦不到的。也就是說,重入鎖具有解除死鎖的功能。
比如你和朋友越好一起去打球,如果你等了半個小時朋友沒有到,你突然接到一個電話,說由於突發情況,朋友不能如期錢來了,那麼你一定掃興的達到回府了。中斷正是提供了一套類似的機制。如果一個縣城正在等待鎖,那麼他依然可以收到一個通知,被告知無需等待,可以停止工作了,這種情況對於處理死鎖是有一定幫助的。
下面的程式碼產生了一個死鎖,得益於鎖的中斷,我們可以輕易的解決這個死鎖:
public class IntLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public IntLock(int lock) {
this.lock = lock;
}
public void run() {
try {
if (this.lock == 1) {
lock1.lockInterruptibly();
Thread.sleep(500);
lock2.lockInterruptibly();
} else {
lock2.lockInterruptibly();
Thread.sleep(500);
lock1.lockInterruptibly();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread())
lock1.unlock();
if (lock2.isHeldByCurrentThread())
lock2.unlock();
System.out.println(this.lock + "執行緒退出");
}
}
public static void main(String[] args) throws InterruptedException {
IntLock r1 = new IntLock(1);
IntLock r2 = new IntLock(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
Thread.sleep(1000);
t2.interrupt();
}
}
執行緒t1和執行緒t2啟動後,t1先佔用lock1,再佔用lock2;t2先佔用lock2,再請求lock1。這樣很容易形成t1和t2之間的互相等待,造成死鎖。在這裡,對鎖的請求,統一使用lockInterruptibly()方法。這是一個可以對中斷進行響應的鎖申請動作,即在等待鎖的過程中可以響應中斷。
在t1和t2執行緒start後,主執行緒39行main進入休眠,此時t1和t2執行緒處於死鎖狀態,然後主執行緒第40行main中斷t2執行緒,故t2會放棄對lock1的請求,同時釋放lock2。這個操作使得t1可以獲得lock2從而繼續執行下去。
執行上訴程式碼,將輸出:
2執行緒退出
1執行緒退出
java.lang.InterruptedException
at com.lxs.demo.IntLock.run(IntLock.java:24)
at java.lang.Thread.run(Thread.java:745)
可以看到,中斷後,兩個執行緒雙雙退出。但真正完成工作的只有t1。而t2放棄任務直接退出,釋放資源。
1.2 鎖申請等待限時
除了等待外部通知之外,還有一種避免死鎖的方法,就是限時等待。通常,我們不會預料到系統在什麼時候會產生死鎖,就無法主動的解除死鎖,最好的系統設計方式是,這個系統根本就不會產生死鎖。我們可以用tryLock()方法進行限時等待。
下面這段程式碼展示了限時等待鎖的使用:
public class TimeLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public void run() {
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
Thread.sleep(6000);
} else {
System.out.println("Get lock failed");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(lock.isHeldByCurrentThread())
lock.unlock();
}
}
public static void main(String [] args){
TimeLock lock1 = new TimeLock();
Thread t1 = new Thread(lock1);
Thread t2 = new Thread(lock1);
t1.start();
t2.start();
}
}
輸出結果:
Get lock failed
在這裡,tryLock()接收兩個引數,一個表示等待時長,另一個表示計時單位。這裡設定為秒,時長為5,表示執行緒在這個鎖的請求中,最多等待5秒。如果超過5秒還沒有得到鎖就返回false。如果成功就返回true。
在本例中,由於佔用鎖的執行緒會持有鎖長達6秒,故另外一個執行緒無法在5秒內獲得鎖,因此,對鎖的請求會失敗。
tryLock()方法也可以不帶引數直接執行,在這種情況下,當前程式會嘗試獲得鎖,如果鎖並未被其他程式佔用,則申請就會成功,立即返回true。如果鎖被其他執行緒佔用,會立即返回false。這種模式不會引起執行緒的等待,因此不會造成死鎖。下面演示了這種使用方式:
public class TryLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public TryLock(int lock) {
this.lock = lock;
}
public void run() {
if (lock == 1) {
while (true) {
if(lock1.tryLock()){
try {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (lock2.tryLock()) {
try {
System.out.println(Thread.currentThread().getId() + " my job done");
return;
} finally {
lock2.unlock();
}
}
}finally {
lock1.unlock();
}
}
}
} else {
while (true) {
if(lock2.tryLock()){
try {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (lock1.tryLock()) {
try {
System.out.println(Thread.currentThread().getId() + " my job done");
return;
} finally {
lock1.unlock();
}
}
}finally {
lock2.unlock();
}
}
}
}
}
public static void main(String [] args){
TryLock lock1 = new TryLock(1);
TryLock lock2 = new TryLock(2);
Thread t1 = new Thread(lock1);
Thread t2 = new Thread(lock2);
t1.start();
t2.start();
}
}
上述程式碼中,採用了非常容易死鎖的加鎖順序。也就是先讓執行緒t1請求lock1,在請求lock2,而讓t2先請求lock2,在請求lock1。在一般情況下,這樣會導致t1,t2互相等待,從而引起死鎖。
但是採用tryLock後,這種情況得到了改善。由於執行緒不會傻傻的等待,而是不停的嘗試,因此,只要執行足夠長的時間,執行緒總是會獲得所需要的資源,從而正常執行(這裡以執行緒能同時獲得lock1和lock2兩把鎖視為正常執行。
程式碼執行結果如下:
12 my job done
11 my job done
1.3 公平鎖
在大多數情況下,鎖的申請都是非公平的。也就是說,執行緒1首先請求了鎖A,接著執行緒2也請求了鎖A。那麼鎖A可用時,執行緒1可以獲得鎖還是執行緒2可以獲得鎖呢?這是不一定的,系統只會從這個鎖的等待佇列中隨機挑取一個。因此不能保證公平性。
而接下來要講的公平鎖,他會按照時間的先後順序,保證先到者先得,後到者後得。所以,公平鎖的最大特點就是,他不會產生飢餓現象。
注意:如果執行緒採用synchronized進行互斥,那麼產生的鎖是非公平的。而重入鎖允許我們進行公平性設定。他有一個如下的建構函式:
public ReentranLock(boolean fair);
當引數fair為true時,表示鎖是公平的。公平鎖看起來很優美,但是要實現公平鎖,必然要求系統維護一個有序佇列,因此對公平鎖得到實現成本比較高,意味著公平鎖的效率非常低下,因此,在預設情況下,鎖是非公平的。如果沒有什麼特別的需求,儘量別用公平鎖。
下面程式碼能很好的凸顯公平鎖的特點:
public class FairLock implements Runnable {
public static ReentrantLock fairLock = new ReentrantLock(true);
// public static ReentrantLock fairLock = new ReentrantLock();
public void run() {
while (true){
try {
fairLock.lock();
System.out.println(Thread.currentThread().getName());
}finally {
fairLock.unlock();
}
}
}
public static void main(String [] args){
FairLock r1 = new FairLock();
Thread t1 = new Thread(r1,"Thread_t1");
Thread t2 = new Thread(r1,"Thread_t2");
t1.start();
t2.start();
}
}
程式碼執行結果:
Thread_t1
Thread_t2
Thread_t1
Thread_t2
Thread_t1
Thread_t2
可以看到,執行緒的排程是公平的。
對上面的ReentantLock的幾個方法整理如下
- lock():獲得鎖,如果鎖已經被佔用,則等待。
- lockInterruptibly():獲得鎖,但優先響應中斷。
- tryLock():嘗試獲得鎖,如果成功,則返回true,失敗返回false。該方法不等待,立即返回
- tryLock(long time, TimeUnit unit):在給定時間內嘗試獲得鎖。
- unlock():釋放鎖。
2 重入鎖的好搭檔:Condition
如果大家瞭解object.wait()方法和object.notify()方法的,那麼就能很容易理解condition物件了。他和wait()和notify()方法的作用是基本相同的。但是wait()和notify()方法是與synchronized關鍵字組合使用的,而condition是與重入鎖相關聯的。
Condition介面提供的基本方法如下:
void await() throws InterrupteException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterrupteException;
boolean await(long time, TimeUnit unit) throws InterrupteException;
boolean awaitUntil(Data deadline) throws InterrupteException;
void signal();
void signalAll();
以上方法含義如下:
-
await()方法會使當前執行緒等待,同時釋放當前鎖,當其他執行緒使用signal()或signalAll()方法時,執行緒會重新獲得鎖並繼續執行。或者當執行緒被中斷時,也能跳出等待。這和object.wait()方法很相似。
-
awaitUninterruptibly()方法與wait()方法相同,唯一的不同點是,該方法不會再等待的過程中響應中斷。
-
signal()方法用於喚醒一個在等待中的執行緒。signalAll()會喚醒所有正在等待的執行緒。這和object.notify()方法很相似。
下面程式碼簡單的演示了Condition的作用:
public class ReenterLockCondition implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
public void run() {
try{
lock.lock();
condition.await();
System.out.println("Thread is going on");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String [] args) throws InterruptedException {
ReenterLockCondition r1 = new ReenterLockCondition();
Thread t1= new Thread(r1);
t1.start();
Thread.sleep(2000);
lock.lock();
condition.signal();
lock.unlock();
}
}
第3行程式碼先通過lock生成一個與之繫結的condition物件。第8行程式碼要求執行緒在condition物件上進行等待。主執行緒main在兩秒後發出signal通知,告知等待在condition上的執行緒可以繼續執行了。
和object.wait()和object.notify()一樣,當執行緒使用Condition.wait()時,要求執行緒持有相關的重入鎖,在condition.wait()呼叫後,這個執行緒會主動釋放這把鎖。並且,在condition.signal()方法呼叫時,也要求執行緒獲取相關的鎖。注意,在signal()方法呼叫之後,一定要釋放相關的鎖第24行,把鎖讓給其他執行緒。如果省略了24行,那麼,雖然已經喚醒了縣城t1,但是由於無法重新獲得鎖,因為也就無法真正的繼續執行。
3 允許多個執行緒同時訪問:訊號量(Semaphore)
訊號量為多執行緒協作提供了更為強大的控制方法。廣義上說,訊號量是對鎖的擴充套件。無論是內部鎖synchronized還是重入鎖ReentranLock,一次都只允許一個執行緒訪問一個資源,而訊號量卻可以指定多個執行緒,同時訪問一個資源。訊號量主要提供了一下的建構函式:
public Semaphore(int permits);
public Semaphore(int permits, boolean fair); //第二個引數可以指定是否公平
在構造訊號量時,必須指定訊號量的准入數,即同時能申請幾個許可。當每個執行緒只申請一個許可時,這就相當於指定了同時能有多少個執行緒可以訪問某個資源。訊號量的主要邏輯方法有:
public void acquire();
public void acquireUninterruptibly();
public boolean tryAcquire();
public boolean tryAcquire(long timeout, TimeUnit unit);
public void release();
acquire()方法嘗試獲得一個准入的許可。若無法獲得,則執行緒會等待,直到申請到許可或者當前執行緒被中斷。acquireUninterruptibly()方法與acquire()方法類似,但不響應中斷。tryAcquire()嘗試獲得一個許可,成功返回true失敗返回false,它不會進行阻塞等待,立即返回。release()用於線上程訪問資源結束後,釋放一個許可,以使其他等待許可的執行緒可以進行資源訪問。
下面是Semaphore的簡單使用:
public class SemapDemo implements Runnable {
//5個一組輸出
final Semaphore semp = new Semaphore(5);
public void run() {
try {
semp.acquire();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getId() + " done!");
semp.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String []args){
ExecutorService exec = Executors.newFixedThreadPool(20);
final SemapDemo demo = new SemapDemo();
for(int i=0;i<20;i++){
exec.submit(demo);
}
}
}
在本例中同時開啟了20個執行緒。觀察上述程式的輸出,你會發現執行緒以5個執行緒為一組依次輸出。
4 ReadWriteLock 讀寫鎖
ReadWriteLock 是JDK5中提供的讀寫分離鎖。讀寫鎖能有效的幫助減少鎖競爭,以提升系統效能。用鎖分離的機制來提升效能非常容易理解,比如縣城A1、A2、A3進行寫操作,執行緒B1、B2、B3進行讀操作,如果使用重入鎖或者內部鎖,從理論上說所有讀之間、讀和寫之間、寫和寫之間都是序列操作。當B1進行讀時,B2、B3則需要等待鎖。由於讀操作並不對資料的完整性造成破壞,這種等待顯然是不合理的。因此讀寫所就用了發揮的餘地。
在這種情況下,讀寫所容許多個執行緒同時讀,是的B1、B2、B3之間並行。但是,考慮到資料完整性,寫寫,和讀寫操作依然是需要相互等待和持有鎖的。總的來說讀寫鎖約束訪問情況如下表
讀 | 寫 | |
---|---|---|
讀 | 非阻塞 | 阻塞 |
寫 | 阻塞 | 阻塞 |
-
讀-讀不互斥:讀讀之間不阻塞。
-
讀-寫互斥:讀阻塞寫,寫也會阻塞讀。
-
寫-寫互斥:寫寫阻塞。
如果系統中,讀操作次數遠遠大於寫操作,則讀寫鎖可以發揮最大的功效,提升系統效能。這裡給出一個稍微誇張的案例,來說明讀寫鎖對效能的幫助。
public class ReadWriteLockDemo {
private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
private int value;
public Object handleRead(Lock lock) throws InterruptedException {
try {
lock.lock();
Thread.sleep(1000);
System.out.println("read success");
return value;
} finally {
lock.unlock();
}
}
public void handleWrite(Lock lock, int index) throws InterruptedException {
try {
lock.lock();
Thread.sleep(1000);
value = index;
System.out.println("write success");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final ReadWriteLockDemo demo = new ReadWriteLockDemo();
Runnable readRunnable = new Runnable() {
public void run() {
try {
demo.handleRead(readLock);
// demo.handleRead(lock);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable writeRunnable = new Runnable() {
public void run() {
try {
demo.handleWrite(writeLock, new Random().nextInt());
// demo.handleWrite(lock, new Random().nextInt());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 18; i++) {
new Thread(readRunnable).start();
}
for (int i = 18; i < 20; i++) {
new Thread(writeRunnable).start();
}
}
}
上述程式碼中,比較了使用讀寫鎖和普通鎖時,系統完成讀寫任務所需要的時間,這裡設定讀任務要比寫任務多得多
從執行結果,可以看到,不用讀寫鎖,程式花費了20秒的時間才完成讀寫任務。採用讀寫鎖,程式需要3秒就完成讀寫任務了。
5 倒計數器:CoundownLatch
CountDownlatch是一個非常實用的多執行緒控制工具類,這裡簡單稱之為倒數計數器,這個工具通常用來控制執行緒等待,他可以讓某一個執行緒等待直到計數結束,在開始執行
一種典型的場景就是火箭發射,在火箭發射前,為了保證萬無一失,往往做多想檢查,引擎才能點火執行,這個場景非常適合CountDownLatch,他可以是點火執行緒在等待所有檢查執行緒全部完工後在執行.
CountDownLatch的建構函式接收一個整數作為引數,即當前這個計數器的計數個數。
public CountDownLatch(int count)
下面示例演示CountDownLatch使用的方法
public class CountDownLatchDemo implements Runnable {
static final CountDownLatch end = new CountDownLatch(10);
static final CountDownLatchDemo demo = new CountDownLatchDemo();
public void run() {
try {
Thread.sleep(new Random().nextInt(10) * 1000);
System.out.println("check complete!");
end.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
exec.submit(demo);
}
end.await();
System.out.println("Fire!");
exec.shutdown();
}
}
上述程式碼第2行生成一個CountDownLatch例項,計數數量為10,這表示需要10個執行緒完成任務後等待在CountDownLatch上的執行緒才能繼續執行,程式碼第10行使用了CountDownLatch.countDown()方法,也就是通知CountDownLatch,一個執行緒已經完成了任務,到計數器減1。第21行使用CountDownLatch.await()方法,要求主執行緒等待所有檢查任務全部完成,待10個任務全部完成後,主執行緒才能繼續執行。
上述案例執行邏輯如下圖簡單表示
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-CrIvOGrD-1609172826418)(assets/CountDownLatch%E6%89%A7%E8%A1%8C%E9%80%BB%E8%BE%91.png)]
主執行緒在CountDownLatch上等待,當所有的檢查執行緒任務全部完成後,主執行緒方能繼續執行
6 執行緒阻塞工具類:LockSupport
首先看下suspend()方法卡死執行緒的例子
public class BadSuspend {
public static Object u = new Object();
static ChangeObjectThread t1 = new ChangeObjectThread("t1");
static ChangeObjectThread t2 = new ChangeObjectThread("t2");
public static class ChangeObjectThread extends Thread{
public ChangeObjectThread(String name) {
super.setName(name);
}
public void run() {
synchronized (u){
System.out.println("in "+ getName());
Thread.currentThread().suspend();
}
}
}
// 導致resume不生效的執行順序可能是這樣的:
// 列印t1 => t1在suspend => t2等待u釋放 => t1被resume => t2被resume => u釋放列印t2 => t2被suspend => 永遠無法結束
public static void main(String []args) throws InterruptedException {
t1.start();
Thread.sleep(100);
t2.start();
t1.resume();
t2.resume();
t1.join();
t2.join();
}
}
上述案例執行示意圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-lDF7S3HD-1609172826422)(assets\suspend死鎖.png)]
主函式呼叫了resume()方法,但是由於時間先後順序的緣,那個resume並沒有生效!這就導致了執行緒t2永遠被掛起,並且永遠佔用了物件u的鎖,這對於系統來來說可能是致命的。
LockSupport是一個非常方便的使用的執行緒阻塞工具,他可以線上程內任意位置讓執行緒阻塞。與Thread.suspend()方法相比,她彌補了由於resume方法發生導致執行緒無法繼續執行的情況。和Object.wait()方法相比,他不需要獲得某個物件的鎖,也不會丟擲InterruptedExeption異常。
現在使用LockSupport重寫這個程式
public class LockSupportDemo {
public static Object u = new Object();
static ChangeObjectThread t1 = new ChangeObjectThread("t1");
static ChangeObjectThread t2 = new ChangeObjectThread("t2");
public static class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name) {
super.setName(name);
}
public void run() {
synchronized (u) {
System.out.println("in " + getName());
LockSupport.park();
}
}
}
public static void main(String[] args) throws InterruptedException {
t1.start();
Thread.sleep(100);
t2.start();
LockSupport.unpark(t1);
LockSupport.unpark(t2);
t1.join();
t2.join();
}
}
這個案例可以正常結束,不會因為pack()方法導致執行緒永久掛起,這是因為LockSupport類使用類似訊號量的機制,他為為每一個執行緒準備一個了一個許可,如果許可可用那麼pack()方法立即返回,並且消費這個許可(也就是把許可變為不可用),如果許可不可用,就會阻塞,而unpack方法則使得一個許可可用,這個特點使得即使unpack發生在pack方法之前,他也可以是下一次的pack()方法立即執行返回。
LockSupport.pack()方法還能支援中斷影響,但是和其他接受中斷函式不一樣,LockSupport.pack()方法不會丟擲InterruptedException異常。他只會默默返回,但是我們可以從Thread.interrupted()等方法中獲得中斷標記
public class LockSupportIntDemo {
public static Object u = new Object();
static ChangeObjectThread t1 = new ChangeObjectThread("t1");
static ChangeObjectThread t2 = new ChangeObjectThread("t2");
public static class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name) {
super.setName(name);
}
public void run() {
synchronized (u) {
System.out.println("in " + getName());
LockSupport.park();
if (Thread.interrupted()) {
System.out.println(getName() + "被中斷了");
}
}
System.out.println(getName() + "執行結束");
}
}
public static void main(String[] args) throws InterruptedException {
t1.start();
Thread.sleep(100);
t2.start();
t1.interrupt();
LockSupport.unpark(t2);
}
}
class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name) {
super.setName(name);
}
public void run() {
synchronized (u) {
System.out.println("in " + getName());
LockSupport.park();
if (Thread.interrupted()) {
System.out.println(getName() + "被中斷了");
}
}
System.out.println(getName() + "執行結束");
}
}
public static void main(String[] args) throws InterruptedException {
t1.start();
Thread.sleep(100);
t2.start();
t1.interrupt();
LockSupport.unpark(t2);
}
}
上述程式碼29行中斷了出於pack()方法狀態的t1,之後,t1可以馬上響應這個中斷,並且返回,t1返回後外面等待的t2才可以進入臨界區,並最終由LockSupport.unpack(t2)操作使其執行結束。
相關文章
- 詳解Condition的await和signal等待/通知機制AI
- vue3保證你看懂watch和watchEffect的詳細詳細使用Vue
- C++11 執行緒同步介面std::condition_variable和std::future的簡單使用C++執行緒
- Git 打補丁-- patch 和 diff 的使用(詳細)Git
- Git 打補丁– patch 和 diff 的使用(詳細)Git
- vuex詳細介紹和使用方法Vue
- 【Linux工具】yum和gdb詳細使用教程。Linux
- 第二十節:詳細講解String和StringBuffer和StringBuilder的使用UI
- JAVA多執行緒詳解(3)執行緒同步和鎖Java執行緒
- 併發控制——樂觀鎖和悲觀鎖
- L298N的接線和詳細使用方法
- 鎖——Lock、Condition、ReadWriteLock、LockSupport
- 詳細介紹php和apache的關係和作用PHPApache
- JavaScript中的new map()和new set()使用詳細(new map()和new set()的區別)JavaScript
- golang 中 channel 的詳細使用、使用注意事項及死鎖分析Golang
- 深入理解Java中的反射機制和使用原理!詳細解析invoke方法的執行和使用Java反射
- Zmap詳細使用者手冊和DDOS的可行性
- 多執行緒中使用Lock鎖定多個條件Condition的使用執行緒
- 【Java】【多執行緒】同步方法和同步程式碼塊、死鎖Java執行緒
- RabbitMQ的詳解和使用MQ
- 使用 khal 和 vdirsyncer 組織和同步你的日曆
- DeFi和CeFi的區別詳細講解
- 使用 python 實現簡單的共享鎖和排他鎖Python
- PostgreSQL 併發控制機制(2):表級鎖和行級鎖SQL
- JS非同步程式設計之Promise詳解和使用總結JS非同步程式設計Promise
- 安裝JDK和Eclipse詳細教程JDKEclipse
- dnsmasq劫持和dns教程詳細解析DNS
- ELK搭建以及執行和ElasticStarch的詳細使用(7.X版本之上)AST
- Java進階篇之十五 ----- JDK1.8的Lambda、Stream和日期的使用詳解(很詳細)JavaJDK
- Zookeeper的選舉機制和同步機制超詳細講解,面試經常問到!面試
- Git詳解和Github的使用Github
- [pythonskill]Python中NaN和None的詳細比較PythonNaNNone
- 如何使用 Ansible 同步 GitHub 和 GitLabGithubGitlab
- Springboot非同步事件配置和使用Spring Boot非同步事件
- 同步和非同步非同步
- Linux安裝和配置zokeeper 詳細教程Linux
- Linux安裝和配置tomcat詳細教程LinuxTomcat
- Kotlin 控制流和陣列操作詳解Kotlin陣列