1.什么是中断机制?
首先,一个线程是否应该中断或停止,不应该由其他线程决定,而应该由线程自身决定,所以Thread.stop(),Thread.suspend(),Thread.resume()都已经被废弃了。其次,在Java中没有办法立刻停止一个线程,但是有些情况,比如需要取消一些耗时耗资源的操作确显得非常有必要,于是Java提供了一种用于停止线程的协商机制-中断,也即中断标识协商机制。
中断 只是一种协商机制,Java并没有给中断增加任何语法,中断的过程完全需要程序员自己来实现。
如要中断一个线程,你需要手动调用该线程的interrupt()
方法,而该方法也仅是将线程对象的中断标识设置为true
,接下来,你需要写代码不断检测当前线程的标识位,如果为true
,代表别的线程请求这条线程中断,此时需要做什么,需要你自己写代码实现。
每个线程对象中都有一个中断标识位,用于表示线程是否被中断。true
表示已中断,false
表示未中断。通过调用线程对象的interrupt()
方法将线程的标识位设置为true
。可以在别的线程中调用,也可以在自己线程中调用。
2.中断常见方法
2.1 public void interrupt()
将线程中断状态设置为true,发起一个协商而不会立即停止线程。
2.2 public static boolean interrupted()
返回当前线程的中断状态,然后清除当前线程的中断状态,即将中断状态重新设置为false。
2.3 public boolean isInterrupted()
返回当前线程的中断状态。
3.大厂面试题中断机制考点
3.1 如何停止中断运行中的线程?
3.1.1 通过volatile变量实现
代码如下:
package multiThreads;
import java.util.concurrent.TimeUnit;
public class InterruptTest {
static volatile boolean isStop = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (true) {
if (isStop) {
System.out.println(Thread.currentThread().getName() + "结束-------");
break;
}
System.out.println(Thread.currentThread().getName() + "还未结束-------");
}
}, "a").start();
TimeUnit.MILLISECONDS.sleep(5L);
new Thread(() -> {
isStop = true;
}, "b").start();
}
}
结果如下:
3.1.2 通过AtoicBoolean实现
代码如下:
package multiThreads;
import com.sun.org.apache.bcel.internal.generic.FADD;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class InterruptTest {
static volatile boolean isStop = false;
static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
public static void main(String[] args) throws InterruptedException {
// m1Volatile();
m2AtomicBoolean();
}
private static void m2AtomicBoolean() throws InterruptedException {
new Thread(() -> {
while (true) {
if (atomicBoolean.get()) {
System.out.println(Thread.currentThread().getName() + "结束-------");
break;
}
System.out.println(Thread.currentThread().getName() + "还未结束-------");
}
}, "a").start();
TimeUnit.MILLISECONDS.sleep(5L);
new Thread(() -> {
atomicBoolean.set(true);
}, "b").start();
}
private static void m1Volatile() throws InterruptedException {
new Thread(() -> {
while (true) {
if (isStop) {
System.out.println(Thread.currentThread().getName() + "结束-------");
break;
}
System.out.println(Thread.currentThread().getName() + "还未结束-------");
}
}, "a").start();
TimeUnit.MILLISECONDS.sleep(5L);
new Thread(() -> {
isStop = true;
}, "b").start();
}
}
结果如下:
3.1.3 通过Thread类自带的中断api实例方法实现
代码如下:
package multiThreads;
import java.util.concurrent.TimeUnit;
public class InterruptTest2 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
Thread currentThread = Thread.currentThread();
if (currentThread.isInterrupted()) {
System.out.println(currentThread.getName() + "被中断,结束时间:" + System.currentTimeMillis());
break;
}
System.out.println(currentThread.getName() + "执行----------");
}
}, "a");
thread.start();
TimeUnit.MILLISECONDS.sleep(10L);
thread.interrupt();
System.out.println("main打断时间:" + System.currentTimeMillis());
}
}
结果如下:
3.2 当前线程的中断标识位为true,是不是线程就立刻停止了?
具体来说,当一个线程调用interrupt()方法的时候:
1.如果线程处于正常活动状态,那么会将中断标识设置为true,仅此而已,被设置中断标识的线程会继续正常运行,不受影响。所以,interrupt()只是协商,并不能立刻直接中断某个线程,需要被调用的线程自己进行配合才行。
2.如果线程处于阻塞状态,例如sleep(),wait(),join()等,那么线程会立刻退出阻塞状态,并抛出InterruptedException,且会将线程中断标识设置为false。
代码示例:
package multiThreads;
import java.util.concurrent.TimeUnit;
public class InterruptTest3 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 300; i++) {
System.out.println(i);
}
System.out.println(Thread.currentThread().getName() + "的中断状态是:" + Thread.currentThread().isInterrupted());
}, "a");
thread.start();
TimeUnit.MILLISECONDS.sleep(1L);
thread.interrupt();
System.out.println(thread.getName() + "的中断状态是:" + thread.isInterrupted() + "----------");
}
}
运行结果:
可以看到线程a调用interrupt()后,中断状态是true,但是线程还是依旧执行for循环。
3.3 对于静态方法Thread.interrupted()的理解?
1.返回当前线程的中断状态
2.将当前线程的中断状态设置为false
4 LockSupport
LockSupport是java.util.concurrent.locks.LockSupport中的一个类,用于创建锁和其他同步类的基本线程阻塞原语。
常见方法:
1.public static void park()
:阻塞线程
2.public static void unpark(Thread thread)
:解除阻塞线程
4.1 三种实现线程等待和唤醒的方法
4.1.1 Object的wait()和notify()
代码如下:
package multiThreads;
import java.util.concurrent.TimeUnit;
public class WaitTest {
public static void main(String[] args) throws InterruptedException {
Object objectLock = new Object();
new Thread(() -> {
synchronized (objectLock) {
System.out.println("come in--------");
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("被唤醒-----");
}
}, "a").start();
TimeUnit.SECONDS.sleep(1L);
new Thread(() -> {
synchronized (objectLock) {
objectLock.notify();
try {
TimeUnit.SECONDS.sleep(2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发出通知");
}
}, "b").start();
}
}
运行结果如下:
结果解析:线程A中wait()方法会导致线程A进入阻塞状态,同时释放锁。另一个线程B获取锁后,执行notify()方法,会唤醒在此监视器(Monitor,此处指objectLock的Monitor)上等待的某一个线程,此处就是线程A,线程A被唤醒后,并不会立刻获取到锁,需要等线程B执行完毕,且释放锁之后,线程A才能重新尝试获取锁,因为没有其他线程争抢,所以线程B执行完毕释放锁之后,线程A自然就能获取到锁,继续执行后续代码。
注意点:
1.当使用object.wait()的时候,当前线程必须是锁对象的Monitor的持有者,否则会抛出 IllegalMonitorStateException
异常
代码如下:
package multiThreads;
import java.util.concurrent.TimeUnit;
public class WaitTest {
public static void main(String[] args) throws InterruptedException {
Object objectLock = new Object();
new Thread(() -> {
//synchronized (objectLock) {
System.out.println("come in--------");
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("被唤醒-----");
// }
}, "a").start();
TimeUnit.SECONDS.sleep(1L);
new Thread(() -> {
// synchronized (objectLock) {
objectLock.notify();
try {
TimeUnit.SECONDS.sleep(2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发出通知");
//}
}, "b").start();
}
}
结果如下:
2.注意wait()需要在notify()的前面执行,否则wait()的线程无人唤醒,将会一直等待下去。
代码如下:
package multiThreads;
import java.util.concurrent.TimeUnit;
public class WaitTest {
public static void main(String[] args) throws InterruptedException {
Object objectLock = new Object();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectLock) {
System.out.println("come in--------");
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("被唤醒-----");
}
}, "a").start();
new Thread(() -> {
synchronized (objectLock) {
objectLock.notify();
System.out.println("发出通知");
}
}, "b").start();
}
}
运行结果:
4.1.2 Condition的await()和signal()
代码如下:
package multiThreads;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class WaitTest {
public static void main(String[] args) throws InterruptedException {
//waitNotify();
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
System.out.println("a come in--");
condition.await();
System.out.println("a resume----");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.lock();
}
}, "a").start();
TimeUnit.SECONDS.sleep(1L);
new Thread(() -> {
lock.lock();
try {
System.out.println("b come in");
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "b").start();
}
}
运行结果如下:
Condition的await()等价于Object的wait(),都是让当前线程释放锁,并进入阻塞状态。
Condition的signal()等价于Object的notify(),都是唤醒在当前锁上等待的阻塞线程。
注意点:Condition中的await()和signal()执行的时候都需要保证当前线程获取了锁,其次注意await()之后一定要有线程来signal()进行唤醒。
4.1.3 使用LockSupport的park()和unpark()来实现
LockSupport.park()方法:
如果没有许可证(permit)会默认阻塞线程,直到别的线程给当前线程发放许可证(permit),当前线程才会被唤醒。
LockSupport.unpark()方法:
给予入参Thread许可证(permit),之前阻塞的LockSupport.park()方法会立刻返回。
示例代码如下:
package multiThreads;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("a come in");
LockSupport.park();
System.out.println("a is invoked");
}, "a");
thread.start();
TimeUnit.SECONDS.sleep(1L);
new Thread(() -> {
LockSupport.unpark(thread);
System.out.println("b invoke a");
}, "b").start();
}
}
运行结果:
可以看到unpark()和park()方法无需获取锁即可执行。
但是如果先执行unpark(),然后执行park()呢?会导致线程a无法被唤醒,一直等待吗?代码验证,如下:
package multiThreads;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a come in");
LockSupport.park();
System.out.println("a is invoked");
}, "a");
thread.start();
TimeUnit.SECONDS.sleep(1L);
new Thread(() -> {
LockSupport.unpark(thread);
System.out.println("b invoke a");
}, "b").start();
}
}
运行结果:
可以看到,依旧可以成功唤醒。park()和unpark()没有执行先后顺序要求。这是为什么呢?请看如下代码:
package multiThreads;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a come in,当前时间:" + System.currentTimeMillis());
LockSupport.park();
System.out.println("a is invoked,当前时间:" + System.currentTimeMillis());
}, "a");
thread.start();
TimeUnit.SECONDS.sleep(1L);
new Thread(() -> {
LockSupport.unpark(thread);
System.out.println("b invoke a,当前时间:" + System.currentTimeMillis());
}, "b").start();
}
}
运行结果:
可以看到,a come in语句和 a is invoked语句的执行时间完全一致,也就是说这两行代码之间的LockSupport.park();
没有生效。这是因为,先执行了下面的unpark()方法,导致这个park()失效了。类似于高速公路,提前买好了通行证unpark,到闸机处直接放行了,没有park拦截了。注意,虽然park()和unpark()没有了顺序要求,但是还是得成双成对的使用,否则某个park没有unpark会导致线程一直阻塞哦。
还有一个注意点,就是unpark获取的许可证(permit)对于被唤醒线程来说,最多只会有一个,即使你unpark()多次,被唤醒线程也最多持有一个许可证(permit)。但是每次从park()中被唤醒,都需要消耗一个permit。所以如果线程A park()两次,同时线程B unpark()两次,那么线程A第一次的park()可以被唤醒,但是第二次的park()无法被唤醒,这点需要注意。示例代码如下:
package multiThreads;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a come in,当前时间:" + System.currentTimeMillis());
LockSupport.park();
LockSupport.park();
System.out.println("a is invoked,当前时间:" + System.currentTimeMillis());
}, "a");
thread.start();
TimeUnit.SECONDS.sleep(1L);
new Thread(() -> {
LockSupport.unpark(thread);
LockSupport.unpark(thread);
System.out.println("b invoke a,当前时间:" + System.currentTimeMillis());
}, "b").start();
}
}
运行结果:
面试题:
1.为什么可以突破wait()和notify()的顺序限制?
因为unpark()会让被唤醒线程获取permit,而唤醒因park()被阻塞的线程需要消耗permit。提前执行unpark()让被唤醒线程获得了一个permit,那么当其在执行park()的时候,因为拥有permit可以直接唤醒。
2.为什么unpark()两次后再park()两次,还是会被阻塞?
因为unpark()获取的permit至多只能拥有一个,调用两次unpark()也只会让被唤醒线程获得一个permit,而每次唤醒因park()阻塞的线程,需要消耗一个permit,因此在第二次park()的时候会被阻塞。