目录
LockSupport与线程中断
线程中断机制
什么是中断机制?
与中断相关的3个API
如何停止中断运行中的线程?
当前线程的中断标识为true,是不是线程就会立刻停止?
如何理解静态方法Thread.interrupted()
LockSupport是什么
线程等待和唤醒机制
3种让线程等待唤醒的方法
Object类中的wait()和notify()方法实现线程的等待和唤醒
Condition接口中的await()后signal()方法实现线程的等待和唤醒
上述两个对象Object和Condition使用的限制条件
LockSupport类中的park()等待和unpack()唤醒
总结
首先,一个线程不应该由其他线程强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运(所以,Thread的stop()、suspend()、resume()都已经废弃了)
其次,在Java中没有办法立即停止一条线程,然而停止线程又显得那么重要(比如需要取消一个耗时/错误操作)。因此,Java提供了一种用于停止线程的协商机制——中断,也即中断标识协商机制。
中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完成完全要求程序员自己实现。若要中断一个线程,需要手动调用该线程的interrupt()方法,该方法也仅仅是将线程对象的中断标识设成true,接着按自己的需要,写代码不断监测当前线程的标识位,如果为true,表示别的线程请求这条线程中断,此时究竟该做什么需要自己写代码实现。
每个线程对象中都有一个中断标识位,用于表示线程是否被中断:该标识位为true标识中断,为false表示未中断。通过调用线程对象的interrupt方法将该线程的标识位设为true,可以在别的线程中带调用,也可以在自己的线程中调用。
执行结果如下图:
我们可以看到interrupt0()是一个native方法
除非线程正在中断(始终允许),否则将带调用此线程的checkAccess()方法(可能还会导致抛出SecurityException)
public class InterruptDemo {
static volatile boolean isStop = false;
public static void main(String[] args) {
Thread tA = new Thread(() -> {
while (true) {
if (isStop) {
System.out.printf(Thread.currentThread().getName() + " isStop 被修改为true!");
break;
}
System.out.println("-------------------->");
}
}, "tA");
tA.start();
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
isStop = true;
}, "B").start();
}
}
执行结果如下图:
public class InterruptDemo {
static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
public static void main(String[] args) {
Thread tA = new Thread(() -> {
while (true) {
if (atomicBoolean.get()) {
System.out.printf(Thread.currentThread().getName() + " atomicBoolean 被修改为true!");
break;
}
System.out.println("-------------------->");
}
}, "tA");
tA.start();
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
atomicBoolean.set(true);
}, "B").start();
}
}
package com.aqin.juc;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @Description
* @Author aqin1012 AQin.
* @Date 10/9/23 11:29 AM
* @Version 1.0
*/
public class InterruptDemo {
public static void main(String[] args) {
Thread tA = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.printf(Thread.currentThread().getName() + " isInterrupted() 被修改为true!程序停止!");
break;
}
System.out.println("-------------------->");
}
}, "tA");
tA.start();
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
tA.interrupt();
}, "B").start();
}
}
执行效果如下图:
不会,具体来说,对一个线程,调用interrupt()时,如果线程处于正常活动状态,那么会将该线程的中断标志设置为true(仅此而已),被设置中断标志的线程将继续正常运行,不受影响。所以,interrupt()并不是真正的中断线程,需要被调用的线程自己进行配合才行。如果线程处于被阻塞状态(如sleep、wait、join等状态),在别的线程中调用当前线程对象的interrupt()方法时,那么线程将会立即退出被阻塞状态,并抛出一个InterruptedException异常。
这个方法是判断线程是否中断并清除当前中断状态的,简单来说就是“复位”。
主要做了两件事:
那么问题来了,这个静态方法interrupted()跟实例方法isInterrupted()有什么区别呢?
我们举个简单的例子对比下执行结果
静态方法interrupted()
执行结果如下:
实例方法isInterrupted()
执行结果如下
来看它俩的源码对比
可以看到其实它们调用的是同一个方法,只不过,在对于是否需要清理这个参数的传递上,静态方法传递的是true,而实例方法传递的是false,简单来讲就是多了一步“复位”操作。因此,才会出现上面两段示例代码执行的结果不一致,在当前线程未执行中断方法interrupt()时,当前线程的中断标识位本身就是初始值false,因此连续调用两次静态方法或者实例方法的执行结果都是false;当当前线程执行了中断方法interrupt()时,第一次调用静态方法和实例方法的返回值同样都是true,但是此时静态方法多做了一步“复位”操作,把当前线程的中断标识位重置回了初始值false,而实例方法则没有这步操作,因此,当第二次调用时,实例方法的示例中当前线程的中断标识位仍然是true,因此仍然返回true,而静态方法的示例代码中当前线程的中断标识位已经被重置回了false,于是就返回了false。
LockSupport是java.util.concurrent.locks中的一个类,用于创建锁和其他同步类的基本线程阻塞原语。
示例代码:
public static void main(String[] args) {
Object objectLock = new Object();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectLock) {
System.out.println(Thread.currentThread().getName() + " 进入--->");
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 被唤醒^ ^");
}
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
synchronized (objectLock) {
objectLock.notify();
}
System.out.println(Thread.currentThread().getName() + " 发出唤醒通知( ̄∇ ̄)/");
}, "B").start();
}
执行结果如下:
问题:
示例代码:
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
try { TimeUnit.SECONDS.sleep(1); } catch ( InterruptedException e) { e.printStackTrace();}
lock.lock();
System.out.println(Thread.currentThread().getName() + " 进入--->");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println(Thread.currentThread().getName() + " 被唤醒^ ^");
}, "A").start();
try { TimeUnit.SECONDS.sleep(1); } catch ( InterruptedException e) { e.printStackTrace();}
new Thread(() -> {
lock.lock();
try {
condition.signal();
} finally {
lock.unlock();
}
System.out.println(Thread.currentThread().getName() + " 发出唤醒通知( ̄∇ ̄)/");
}, "B").start();
}
执行结果如下:
问题:
在lock、unlock对里面才能正确调用condition中线程等待和唤醒的方法。
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每一个线程都有一个Permit(许可),但与Semaphore不同的是,许可的累加上限是1(最多一个许可)。
使用示例:
public static void main(String[] args) {
Thread A = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 进入--->");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + " 被唤醒^ ^");
}, "A");
A.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
LockSupport.unpark(A);
System.out.println(Thread.currentThread().getName() + " 发出唤醒通知( ̄∇ ̄)/");
}, "B").start();
}
执行结果如下:
可以看到,使用LockSupport进行线程阻塞/唤醒不需要在锁对块(synchronized/lock)中,所以上面Object类和Condition接口的第一个问题解决了,然后我们看看第二个问题:加锁/解锁的先后顺序。
如上图可以看出,park()和unpack()执行的先后顺序不会影响结果。
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport是一个线程阻塞工具,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞后也有对应的唤醒方法,归根结底,LockSupport调用的是Unsafe中的native代码。
LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程,每个使用LockSupport的线程都一个Permit(许可)与LockSupport相关联,每一个线程都都一个相关的Permit,并且最多一个,重复调用unpark()也不会积累Permit。
换句话讲,线程阻塞需要消耗凭证,并且这个凭证最多1个
当调用park()方法时:
当调用unpark()方法时:
最后,我们思考2个问题:
搞定( ̄∇ ̄)/~~~~~~~~~~