一些术语
进程与线程
进程是程序运行的实例,是OS中进行资源分配和调度的基本单位。
比如当启动QQ时,会将QQ相关的代码从磁盘加载至内存,这时就开启了一个进程。
线程是执行调度的基本单位,进程中包含至少一个线程,同一个进程中的所有线程共享该进程中的资源。
同步和异步
同步和异步通常用来形容方法调用。
同步方法一旦调用,调用者必须等待结果返回后才能继续后续的行为。类似去餐馆吃饭。
异步方法一旦调用,不需要等待结果返回调用者可以继续后续的操作。异步方法调用更像是一个消息传递。
如果异步调用需要返回结果,那么当该异步调用完成后,则会通知调用者。类似点外卖。
并发和并行
并发(concurrent)是同一时间应对(dealing with)多件事情的能力。偏重于多个任务交替执行。
并行(parallel)是同一时间动手做(doing)多件事情的能力 。
- 家庭主妇做饭、打扫卫生、给孩子喂奶,她一个人轮流交替做这多件事,这是并发
- 家庭主妇雇了个2保姆,她们一起这些事,这时既有并发,也有并行(例如锅只有一口,一个人用锅时,另一个人就得等待)
- 家庭主妇雇了3个保姆,一个专做饭、一个专打扫卫生、一个专喂奶,互不干扰,这是并行
栈帧
线程启动后,虚拟机就会为其分配一块栈内存
- 每个栈由多个栈帧(Stack Frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
线程上下文切换
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的资源,以便下次切换回这个任务时可以恢复现场。所以任务从保存到再加载的过程就是一次上下文切换。 上下文切换会影响多线程的执行速度 。由于多线程执行时存在上下文切换的开销,所以多线程有时候不一定就比单线程快。
线程上下文切换时机:
- 线程的 cpu 时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
如何减少上下文切换:
- 无锁并发编程
- 多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据。
- CAS算法
- Java的Atomic包使用CAS算法来更新数据,而不需要加锁。
- 使用适量的线程
- 避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。
- 使用协程
- 在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
线程常见的属性
id
每个线程有自己id,用于标识不同的线程,唯一且不允许被修改,id 从1开始自增。
name
创建线程时,可以给线程取一个唯一的name,方便后续定位问题。
priority
- java中线程优先级有10个级别,默认为5
- 程序设计不应该依赖优先级
- java线程的优先级会映射到操作系统,而不同操作系统的优先级是不一样的
- 优先级会被有的操作系统改变,甚至忽略,这会使得程序变的不可靠
public class Main {
public static void main(String[] args) {
MyThread thread1 = new MyThread("111");
MyThread thread2 = new MyThread("222");
MyThread thread3 = new MyThread("333");
thread1.setPriority(Thread.MAX_PRIORITY);
thread2.setPriority(Thread.NORM_PRIORITY);
thread3.setPriority(Thread.MIN_PRIORITY);
thread1.start();
thread2.start();
thread3.start();
}
}
输出结果,可能是:
333
111
222
isDaemon
守护线程与用户线程整体无区别,唯一区别在于是否影响 JVM 的退出。当JVM中不存在非Deamon线程(即用户线程)的时候,JVM将会退出,不会管守护线程是否运行完毕。
它们的作用通常也不同。用户线程执行主要逻辑,守护线程通常作一些程序上的服务性工作,比如心跳检测。
线程状态(生命周期)
java中的线程有六种状态:
通常,把Blocked(被阻塞)、Waiting(等待)、Timed_waiting(计时等待)都称为阻塞状态。
创建线程
准确的说,在java中创建线程只有一种实现方式,就是实例化Thread。它有两种实现执行单元的方式:
继承Thread类并重写run方法
public class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.getName());
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread("111");
myThread.start();
}
}
实现Runnable接口的run方法
public class MyTask implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
Runnable task = new MyTask();
new Thread(task).start();
new Thread(task).start();
}
}
两种方法的对比和本质
实现Runnable接口更好
- 基于组合方式将线程(Thread)和任务(Runnable,要执行的代码)进行了解耦
- 从资源利用角度,实现Runnable接口可以与线程池等高级 API 配合,节省资源
- 从java语法角度,Java不支持双继承,实现Runnable接口后可以继承其他类,更加灵活
两种方法的本质
最终调用target.run()
- 创建Thread类是将整个run方法重写
- 实现Runnable是将自身注入Thread,并执行run方法
target变量是Thread类中Runnable对象的引用
其他写法
线程的创建方式,在代码中写法千变万化,但其本质都是通过创建Thread类或实现Runnable接口来实现的。
- 使用线程池创建线程。底层也是通过 new Thread() 来创建线程的。
- 定时器
- 匿名内部类
- lambda表达式
- 实现Callable接口并使用FutureTask
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "hello";
}
}
public class Main {
public static void main(String[] args) {
// 创建异步任务
FutureTask<String> futureTask = new FutureTask<String>(new MyCallable());
// 启动线程
new Thread(futureTask).start();
try {
// 等待线程执行 并返回结果
String result = futureTask.get();
System.out.println("result=" + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
Q:有多少种实现线程的方法?
本质上只有新建Thread类这一种方式,但是通常我们把它区分为两种形式,一种是实现Runnable接口,一种是继承Thread类。一般优先选择实现Runnable接口,因为第一它将任务的创建和执行解耦,第二线程的创建是很耗资源的,实现Runnable接口后可以与线程池等高级 API 配合,第三由于java的单继承性,实现接口会具有更好的扩展性。虽然说实现的方式是两种,但是其本质上都是一样的。都是调用了Thread类的run()方法,该方法会先判断Runnable对象是否为null,存在则执行Runnable对象的run方法。只不过实现Runnable时,是直接调用了自身的run方法,而继承Thread类是重写了run方法。除了常见的这两种方式,还有线程池、定时器、Callable等多种表现形式,但是它们底层仍然还是刚才说的那两种实现。
策略模式
Runnable 接口将业务执行单元和线程控制解耦,通过向 Thread 类中传入不同的实现类,线程会执行不同的任务,这是一种策略模式的实现思想。
启动线程——start
start原理
线程调用 start() 方法后,线程进入就绪状态,何时能够运行由操作系统的线程调度器来决定。
重复调用 start() 方法,会抛出 IllegalThreadStateException
。因为在调用start()方法时会先判断当前线程的状态是否为0,如果不是则抛出 IllegalThreadStateException
。而线程只有在第一次被创建时状态才为0。
一旦run方法执行完毕,线程就结束了。这里的执行完毕包含正常结束(run 方法返回)和抛出异常而导致的中止
只有调用了start()后,JVM才会将线程对象和操作系统中实际的线程进行映射,此时才会是一个线程,拥有自己的栈帧。通过 new Thread 创建的线程,此时还不是真正的线程,只是一个线程实例。
start()方法的原理:
- 检查线程状态(栈、PC)
- 将该线程加入线程组
- 调用 native 方法 start0(),即和操作系统映射起来
以上,再次说明了在java中线程只有一种实现方式,就是实例化Thread,并且提供其执行的run方法。无论是通过继承thread还是实现runnable接口,最终都是重写或者实现了run方法。而真正启动线程都是通过实例化Thread并调用其start方法实现的。
模板方法模式
在 Thread 类中,使用了类似模板方法的设计模式。固定的算法已定,将具体的任务抽象出去,交给子类去实现。
在 Thread 类中提供了 start 方法来启动一个线程来执行一个任务,而这个任务是什么,Thread 类并不关心,它只是提供了 run 方法交给外部来实现。所以创建线程是实现 run 方法,但是启动的是 start 方法。
查看线程
在java中可以使用如下命令来查看:
- jps:命令查看所有 Java 进程
- jstack :查看某个 Java 进程(PID)的所有线程状态
- jconsole:查看某个 Java 进程中线程的运行情况(图形界面)
每个线程都拥有自己的程序计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。
线程休眠——sleep
sleep方法可以让线程进入Timed_Waiting状态,让出CPU资源,但是不释放锁,直到规定时间后会继续抢占CPU时间片,休眠期间如果被中断,会抛出异常并清除中断标志。
线程让步——yield
当前线程调用yield方法时,意味着该线程愿意放弃 CPU 资源,不过这只是给 CPU 一个提示,当 CPU 资源并不紧张时会无视 yield 提醒。同 sleep 一样也不释放锁,调用yield的线程状态仍然是Runnable状态,在下一次线程调度中会和其他线程一起抢占CPU的执行权。
与sleep的区别:yield可能随时被再次调度,而sleep在waiting时间时不会被再次调度
等待其他线程结束——join
作用:因为新的线程加入了我们,所以我们要等他执行完再出发
case1
下面的代码执行,打印 r 是什么?
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1000);
log.debug("结束");
r = 10;
});
t1.start();
log.debug("结果为:{}", r);
log.debug("结束");
}
因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10,而主线程一开始就要打印 r 的结果,所以只能打印出 r=0。
解决方式:在 t1.start() 后 t1.join() 即可。这样main线程会等待 t1 线程执行完后才能向下执行。
case2
下面代码 cost 大约多少秒?
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(1000);
r1 = 10;
});
Thread t2 = new Thread(() -> {
sleep(2000);
r2 = 20;
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
结果(cost接近2s):
r1: 10 r2: 20 cost: 2002
分析
- 第一个 join:等待 t1 时, t2 并没有停止, 而在运行
- 第二个 join:1s 后, 执行到此, t2 也运行了 1s, 因此也只需再等待 1s
case3:线程在join时间结束前结束
下面代码 cost 大约多少秒?
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test3();
}
public static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(1000);
r1 = 10;
});
long start = System.currentTimeMillis();
t1.start();
t1.join(1500);
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
由于 线程执行结束会导致 join 结束,所以耗时接近1s。
如果让 t1 sleep两秒,结果如何?结果cost接近1.5s
case4:join时被中断
public class JoinInterrupt {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
mainThread.interrupt();
Thread.sleep(5000);
System.out.println("Thread1 finished.");
} catch (InterruptedException e) {
System.out.println("子线程中断");
}
}
});
thread1.start();
System.out.println("等待子线程运行完毕");
try {
thread1.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"主线程中断了");
}
System.out.println("子线程已运行完毕");
}
}
结果
等待子线程运行完毕
main主线程中断了
子线程已运行完毕
java.lang.InterruptedException
Thread1 finished.
注意,中断的是主线程。而且抛出异常之后过了一段时间会输出"Thread1 finished",也就是说主线程结束后,子线程此时并没有运行完毕。所以应该在主线程的catch处加上 thread1.interrupt();
主动中断子线程,避免出现不一致的情况。
总结
join():当前线程会无限等待(Waiting状态),直到目标线程执行完毕
join(long):如果超过给定时间目标线程还在执行,当前线程也会因为等不及了而继续往下执行
原理
可见,目标线程(thread1)未结束时会调用 wait 方法来暂停等待线程(main线程),直到目标线程已终止。
疑惑:没有notify,等待线程是怎么被唤醒的呢?
实际上JVM会在线程的 run 方法运行结束后执行该线程的 notifyAll 方法来通知所有的等待线程。
扩展
让一个线程等待其他线程,除了join,还可以使用成熟的工具类 CountDownLatch
或CyclicBarrier
。在实际使用中,最好不要使用底层的方法,而是使用提供的工具类。
停止线程
interrupt
在无外界干涉的情况下,代码运行结束或抛出异常线程就停止了。
正确的停止线程:使用interrupt来通知某个线程停止,而不是强制停止该线程
通过设置线程的中断标志并不能直接终止该线程的执行,而是由被中断的线程根据中断情况自行处理。也就是说,中断的并不是线程的逻辑,而是线程的阻塞。
- void interrupt():该方法仅会设置线程的中断标志位为true,而不会中断线程。如果线程因为调用sleep、wait、join方法而被阻塞挂起,其他线程调用该线程的interrupt方法后,该线程会在调用这些方法的地方抛出
InterruptedException
异常而返回 - **boolean isInterrupted():**通过检查中断标志位判断当前线程是否被中断
- **static boolean interrupted():**检查当前线程是否被中断。与 isInterrupted() 不同的是,该方法如果发现线程被中断,会清除线程的中断标志位状态
在实际中,很少会有停止线程的操作。interrupt也只是起到辅助的功能,具体的停止逻辑还是需要人为来控制。
正确停止线程的好处:
- 被中断的线程拥有如何中断线程的权利
- 保证了数据的安全
通过中断终止线程
线程的中断状态是线程的一个标识位,表示一个运行中的线程是否被其他线程进行了中断操作。线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。
如果该线程已经处于终止状态,即使该线程被中断过,在调用该线程对象的isInterrupted()时依旧会返回false。
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread() + "running");
}
});
thread.start();
Thread.sleep(100);
System.out.println("interrupt thread");
thread.interrupt();
thread.join();
System.out.println("main thread end");
}
}
输出:
// 省略更多的Thread[Thread-0,5,main]running
Thread[Thread-0,5,main]running
Thread[Thread-0,5,main]running
interrupt thread
Thread[Thread-0,5,main]running
main thread end
中断操作是一种简便的线程间交互方式,最适合用来取消或停止任务。
注意,在while内tyr/catch会遇到的问题:即使中断了线程,线程也不会停止。可以通过break或return来终止。
通过中断节省时间
假设线程sleep了10s,但是其任务有可能在10s内就完成了,此时休眠10s再返回就有点浪费了。这时通过调用线程的interrupt方法,强制sleep方法抛出异常而返回,使得线程恢复到激活状态
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("业务逻辑在10s内完成");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("被中断了:" + Thread.currentThread().isInterrupted());
}
});
thread.start();
Thread.sleep(2000);
thread.interrupt();
thread.join();
System.out.println("main is over");
}
}
程序结束,输出结果:
业务逻辑在10s内完成
被中断了:false
main is over
可中断的方法(如sleep、join、wait)在抛出InterruptedException后,会将该线程的中断标识位清除改回 false。
上例中,如果不触发中断异常,该线程在sleep时如果被中断,其中断标志位会变为true.
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("业务逻辑在10s内完成");
Thread.sleep(10000);
System.out.println("被中断了:" + Thread.currentThread().isInterrupted());
});
thread.start();
Thread.sleep(2000);
thread.interrupt();
thread.join();
System.out.println("main is over");
}
}
程序结束,输出结果:
业务逻辑在10s内完成
被中断了:true
main is over
打断park线程
private static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park(); //执行到此处后不会继续向下执行
log.debug("unpark...");
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
}, "t1");
}
使用interrupt可以打断 park 线程, 不会清空打断状态
private static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
Thread.sleep(500);
t1.interrupt();
}
输出
[t1] c.TestInterrupt - park...
[t1] c.TestInterrupt - unpark...
[t1] c.TestInterrupt - 打断状态:true
如果打断标记已经是 true, 则 park 会失效
private static void test4() {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
LockSupport.park();
log.debug("unpark...");
});
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
输出:
[Thread-0] c.TestInterrupt - park...
[Thread-0] c.TestInterrupt - unpark...
[Thread-0] c.TestInterrupt - 打断状态:true
[Thread-0] c.TestInterrupt - unpark...
如果第6行的代码改为调用Interrupted,打断标记会被还原为fasle,park仍然生效,最后的unpark不会被输出
理解interrupted()和isInterrupted()
案例1
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(() -> {
while (true) {
}
});
a.start();
a.interrupt();
System.out.println("isInterrupted:" + a.isInterrupted());
System.out.println("isInterrupted:" + a.interrupted());
System.out.println("isInterrupted:" + Thread.interrupted());
System.out.println("isInterrupted:" + a.isInterrupted());
a.join();
System.out.println("main is over");
}
}
程序不终止,输出结果:
isInterrupted:true
isInterrupted:false
isInterrupted:false
isInterrupted:true
interrupted 方法内部是获取当前线程的中断状态,这里虽然调用了a线程的interrupted()方法,但是获取的是主线程的标志,因为主线程是当前线程。即 a.interrupted() 与 Thread.interrupted() 的作用是一样的。
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(() -> {
while (!Thread.currentThread().interrupted()) {
}
System.out.println("a isInterrupted:" + Thread.currentThread().isInterrupted());
});
a.start();
a.interrupt();
a.join();
System.out.println("main is over");
}
}
程序终止,输出结果:
a isInterrupted:false
main is over
案例2
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
}
}, "t1");
Thread t2 = new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(500);
t1.interrupt();
System.out.println("打断线程t1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2");
t1.start();
t2.start();
t1.join();
}
预期启动后,线程 t2 会打断线程 t1,实际结果却是t2未打断t1。这是为什么呢?
同上,在main线程中调用 t1.join()时的当前线程是main线程,而t2打断的线程是t1线程,所以不生效。
将其改为如下代码,即可打断 t1 线程
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
}
}, "t1");
Thread main = Thread.currentThread();
Thread t2 = new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(500);
main.interrupt();
System.out.println("打断线程t1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2");
t1.start();
t2.start();
t1.join();
}
错误的停止线程
使用过时的API
- stop():导致线程运行时突然停止,可能会产生脏数据问题
- supend():带着锁挂起线程,容易造成线程死锁
- resume()
使用volatile修饰的标志位
case1:正确的场景
// 线程每隔1s输出0~100000中是100的倍数的数字,5s后程序结束
public class WrongWayVolatile implements Runnable {
private volatile boolean canceled = false;
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !canceled) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数。");
}
num++;
Thread.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
WrongWayVolatile r = new WrongWayVolatile();
Thread thread = new Thread(r);
thread.start();
Thread.sleep(5000);
r.canceled = true;
}
}
在这种情景下,确实会使得线程停止。
case2:不正确的场景
生产者
class Producer implements Runnable {
public volatile boolean canceled = false;
BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !canceled) {
if (num % 100 == 0) {
storage.put(num);
System.out.println(num + "是100的倍数,被放到仓库中了。");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("生产者结束运行");
}
}
}
消费者
class Consumer {
BlockingQueue storage;
public Consumer(BlockingQueue storage) {
this.storage = storage;
}
public boolean needMoreNums() {
if (Math.random() > 0.95) {
return false;
}
return true;
}
}
主程序
public class WrongWayVolatileCantStop {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
Producer producer = new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(1000);
Consumer consumer = new Consumer(storage);
while (consumer.needMoreNums()) {
System.out.println(consumer.storage.take()+"被消费了");
Thread.sleep(100);
}
System.out.println("消费者不需要更多数据了。");
//一旦消费不需要更多数据了,应该让生产者也停下来
producer.canceled=true;
System.out.println(producer.canceled);
}
}
上例中,生产者的生产速度很快,消费者消费速度慢,所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费。也就是生产者在 storage.put(num);
时阻塞了,无法进行下一次的循环判断。在此种情况下,通过volatile来停止线程是无效的。
在此情况下,想要停止线程,应该使用中断:
-
主线程
r.canceled = true;
改为producer.interrupt()
-
生产者
!canceled
改为Thread.currentThread().isInterrupt()
Q:如何停止线程
思路:
- 先回答结果
- 再说明这种方式的好处
- 扩展说说错误的方式,以及错误的原因
可以用interrupt来停止线程。
这种方式一方面可以保证数据的安全,另一方面应该把线程停止的主动权交给被中断的线程,由它自身根据情况来处理。要想达到这种效果,需求请求方、被停止方、子方法被调用方相互配合。具体的说,请求方发送中断请求信号,被停止方必须在每次循环或适当的时候去检查这个中断信号,并且在可能抛出InterruptException的时候去处理这个信号,以便于自己可以被停止。如果我们是写子方法的,有两种最佳实践,优先是在方法层抛出InterruptException,以便其他人去做进一步的处理,或者在收到中断信号之后将其再次设为中断状态。
如果不用interrupt,也可以使用stop,但是它会带来数据安全问题;或者使用suspend,但是它会使得线程带锁挂起导致死锁;或者使用volatile修饰的boolean,但是它无法处理长时间阻塞的情况。
Q:如何处理不可中断的阻塞
比如抢锁时ReentrantLock.lock()或者Socket I/O时无法响应中断,那应该怎么让该线程停止呢?
没有通用的方法,要具体场景具体分析。比如 ReentrantLock.lock() ,在 lock 过程中发生阻塞时是没有办法让其及时响应的,但是可以使用该类的 lockInterrupt() 方法来响应中断。也就是说,在编码应该选择这种可以响应中断的方法,尽可能地让线程可以响应中断。