搞懂多线程(五)之线程中断、等待、唤醒
一、线程中断
方式一:volatile关键字
通过volatile关键字的可见性修饰一个标识,要停止的线程监听该标识
方式二:AtomicBoolean
原子类
通过
AtomicBoolean
原子布尔类,修饰一个标识,要停止的线程监听该标识
方式三:interrupt()
通过Thread中的
interrupt()
实现1、通过
Thread.currentThread().isInterrupted()
判断线程中断状态2、调用要中断线程的
interrupt()
来设置中断状态(要中断的线程不一定立马中断)
Thread中中断方法的特殊说明
public void interrupt():中断此线程
仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程
如果该线程处于阻塞状态(调用
wait()
join()
sleep()
引起的)那么它的中断状态将被清除
,并且将收到InterruptedException
,如有必要需在wait()
join()
sleep()
的finally中手动中断状态中断不活动的线程不会产生任何影响,线程如果已经结束,则返回
false
public boolean isInterrupted():判断当前线程是否被中断(通过检查中断标志位)
public static boolean interrupted():判断线程是否被中断并清除当前中断状态。
1、返回当前线程的中断状态,测试当前线程是否已被中断
2、将当前线程的中断状态清零并重新设为false,清除线程的中断状态
如果连续两次调用此方法,则第二次调用将返回false,因为连续调用两次的结果可能不一样
二、线程等待和唤醒
方式一:使用Object中的wait()和notify()
与synchronized
配合使用(需要先持有锁)wait()
和notify()
实现等待和唤醒,wait()
应在notify()
方法调用之前调用
public static void main(String[] args) {
Object o = new Object();
new Thread(() -> {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + " AA进入");
try {
o.wait();
System.out.println(Thread.currentThread().getName() + " AA结束");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "AA").start();
new Thread(() -> {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + " BB进入");
o.notify();
}
}, "BB").start();
}
输出结果
AA AA进入
BB BB进入
AA AA结束
方式二:使用ReentrantLock.Condition中的await()和signal()
与ReentrantLock
配合使用,(需要先持有锁)调用newCondition()
中的await()
和signal()
实现等待和唤醒,await()
应在signal()
方法调用之前调用
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock();
Condition condition = reentrantLock.newCondition();
new Thread(() -> {
try {
reentrantLock.lock();
System.out.println(Thread.currentThread().getName() + " AA进入");
condition.await();
System.out.println(Thread.currentThread().getName() + " AA结束");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
reentrantLock.unlock();
}
}, "AA").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
try {
reentrantLock.lock();
System.out.println(Thread.currentThread().getName() + " BB进入");
condition.signal();
} finally {
reentrantLock.unlock();
}
}, "BB").start();
}
输出结果
AA AA进入
BB BB进入
AA AA结束
方式三:使用LockSupport中的park()和unpark()
LockSupport:用于创建锁和其他同步类的基本线程阻塞原语。形象的理解:线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。
调用park()方法
如果有凭证,则会直接消耗掉这个凭证然后正常退出
如果无凭证,就必须阻塞等待凭证可用
调用unpark()方法
它会增加一个凭证,但凭证最多只能有1个,累加无效
park()
和unpark()
使用时无需持有锁,顺序也可调换,但是就算unpark()
多次调用也是只能只有一个许可证只会触发一次park()
效果
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " AA进入");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + " AA结束");
}, "AA");
t1.start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " BB进入");
LockSupport.unpark(t1);
}, "BB").start();
}