Java LockSupport與線程中斷
什么是中斷?
首先
一個線程不應(yīng)該由其他線程來強制中斷或停止,而是應(yīng)該由線程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.resume 都已經(jīng)被廢棄了。
其次
在Java中沒有辦法立即停止一條線程,然而停止線程卻顯得尤為重要,如取消一個耗時操作。因此,Java提供了一種用于停止線程的機制——中斷。
中斷只是一種協(xié)作機制,Java沒有給中斷增加任何語法,中斷的過程完全需要程序員自己實現(xiàn)。
若要中斷一個線程,你需要手動調(diào)用該線程的interrupt方法,該方法也僅僅是將線程對象的中斷標識設(shè)成true;
接著你需要自己寫代碼不斷地檢測當前線程的標識位,如果為true,表示別的線程要求這條線程中斷,
此時究竟該做什么需要你自己寫代碼實現(xiàn)。
每個線程對象中都有一個標識,用于表示線程是否被中斷;該標識位為true表示中斷,為false表示未中斷;通過調(diào)用線程對象的interrupt方法將該線程的標識位設(shè)為true;可以在別的線程中調(diào)用,也可以在自己的線程中調(diào)用。
public void interrupt() | 實例方法,interrupt()僅僅是設(shè)置線程的中斷狀態(tài)為true,不會停止線程 |
public static boolean interrupted() | 靜態(tài)方法,Thread.interrupted(); 判斷線程是否被中斷,并清除當前中斷狀態(tài) 這個方法做了兩件事: 1 返回當前線程的中斷狀態(tài) 2 將當前線程的中斷狀態(tài)設(shè)為false |
public boolean isInterrupted() | 實例方法, 判斷當前線程是否被中斷(通過檢查中斷標志位) |
如何使用中斷標識停止線程?
三種方式
- 通過一個volatile變量實現(xiàn)
package com.atguigu.juc.senior.test;
import java.util.concurrent.TimeUnit;
public class InterruptDemo{
private static volatile boolean isStop = false;
public static void main(String[] args){
new Thread(() -> {
while(true) {
if(isStop) {
break;
}
System.out.println("-------hello interrupt");
}
},"t1").start();
//暫停幾秒鐘線程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
isStop = true;
}
}
- 通過AtomicBoolean
package com.zzyy.study.test;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class StopThreadDemo{
private final static AtomicBoolean atomicBoolean = new AtomicBoolean(true);
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while(atomicBoolean.get()) {
try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("-----hello");
}
}, "t1");
t1.start();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
atomicBoolean.set(false);
}
}
- 通過Thread類自帶的中斷api方法實現(xiàn)
當前線程的中斷標識為true,是不是就立刻停止?
具體來說,當對一個線程,調(diào)用 interrupt() 時:
① 如果線程處于正?;顒訝顟B(tài),那么會將該線程的中斷標志設(shè)置為 true,僅此而已。
被設(shè)置中斷標志的線程將繼續(xù)正常運行,不受影響。所以, interrupt() 并不能真正的中斷線程,需要被調(diào)用的線程自己進行配合才行。
② 如果線程處于被阻塞狀態(tài)(例如處于sleep, wait, join 等狀態(tài)),在別的線程中調(diào)用當前線程對象的interrupt方法,那么線程將立即退出被阻塞狀態(tài),并拋出一個InterruptedException異常。
需要注意的是Sleep方法拋出InterruptedException異常后,中斷標識也被清空置為false, 需要在catch中再次調(diào)用 .interrupt( )方法進行中斷,否則就會導(dǎo)致無線循環(huán)。
LockSupport是什么
LockSupport是用來創(chuàng)建鎖和其他同步類的基本線程阻塞原語。
LockSupport中的park() 和 unpark() 的作用分別是阻塞線程和解除阻塞線程
3種讓線程等待和喚醒的方法
- 方式1:使用Object中的wait()方法讓線程等待,使用Object中的notify()方法喚醒線程
- 方式2:使用JUC包中Condition的await()方法讓線程等待,使用signal()方法喚醒線程
- 方式3:LockSupport類可以阻塞當前線程以及喚醒指定被阻塞的線程
Object類中的wait和notify方法實現(xiàn)線程等待和喚醒
static Object objectLock = new Object();
public static void syncWaitNotify() {
new Thread(() -> {
//暫停幾秒鐘線程
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (objectLock){
System.out.println(Thread.currentThread().getName()+"\t"+"---come in");
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t"+"---被喚醒");
}
},"t1").start();
//暫停幾秒鐘線程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
synchronized (objectLock){
objectLock.notify();
System.out.println(Thread.currentThread().getName()+"\t"+"---發(fā)出通知");
}
},"t2").start();
}
- wait和notify方法必須要在同步塊或者方法里面,且成對出現(xiàn)使用
- 先wait后notify才OK
Condition接口中的await后signal方法實現(xiàn)線程的等待和喚醒
public static void lockAwaitSignal() {
new Thread(() -> {
//暫停幾秒鐘線程
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {
e.printStackTrace(); }
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"\t"+"---come in");
condition.await();
System.out.println(Thread.currentThread().getName()+"\t"+"---被喚醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
},"t1").start();
new Thread(() -> {
lock.lock();
try{
condition.signal();
System.out.println(Thread.currentThread().getName()+"\t"+"---發(fā)出通知");
}finally {
lock.unlock();
}
},"t2").start();
}
- Condtion中的線程等待和喚醒方法之前,需要先獲取鎖
- 一定要先await后signal,不要反了
總結(jié):Object和Condition使用的限制條件
- 線程先要獲得并持有鎖,必須在鎖塊(synchronized或lock)中。
- 必須要先等待后喚醒,線程才能夠被喚醒。
LockSupport類中的park等待和unpark喚醒
是什么
LockSupport是用來創(chuàng)建鎖和其他同步類的基本線程阻塞原語。
LockSupport類使用了一種名為Permit(許可)的概念來做到阻塞和喚醒線程的功能, 每個線程都有一個許可(permit),permit只有兩個值1和零,默認是零。可以把許可看成是一種(0,1)信號量(Semaphore),但與 Semaphore 不同的是,許可的累加上限是1。
主要方法
阻塞 park() /park(Object blocker) 阻塞當前線程/阻塞傳入的具體線程
喚醒 unpark(Thread thread) 喚醒處于阻塞狀態(tài)的指定線程
示例
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "---come in");
LockSupport.park();
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "\t" + "---被喚醒");
}, "t1");
t1.start();
new Thread(() -> {
LockSupport.unpark(t1);
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName()+"\t"+"---發(fā)出通知");
},"t2").start();
}
- 無鎖塊要求
- 之前錯誤的先喚醒后等待,LockSupport照樣支持