✨✨hello,愿意点进来的小伙伴们,你们好呐!
🐻🐻系列专栏:【JavaEE初阶】
🐲🐲本篇内容:介绍 线程的 中断,等待的区别
🐯🐯作者简介:一名现大二的三非编程小白,日复一日,仍需努力。
- 理解中断 :
- 自定义Boolean变量中断线程:
- 线程等待 / 通知机制:
- wait() 细节:
- 线程插队现象:
理解中断 :
中断线程 可以理解为 :
在Java线程中有一个布尔类型的标识位属性,这个标识为属性代表着一个正在运行中的线程是否被其他线程进行了中断操作,这个标识符在创建线程的时候是false,代表着该线程未被中断 , 我们可以调用 interrupted() 方法将该标识符置为 true,表示目前这个标识符被重置了,代表着程序被提醒中断 , 在程序中我们都使用 isInterrupted() 来判断该标识符是否为中断标识符.
public class ThreadDemo31 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {//为中断的标识符是false,需要取反才会
System.out.println("hello");
}
});
thread1.start();
Thread.sleep(1);
System.out.println("interrupt前");
thread1.interrupt();
System.out.println("interrupt后");
}
}
在上面的程序中,thread1被启动,然后这个时候标识符属性为false 就取反,让循环进行,然后再main线程休眠1ms后将标识符置为true,表示被中断.
上述的线程中断方法会导致,线程直接中断,但是在某些场景下通知中断后不一定要立刻中断,就比如说:
小明的妈妈通知小明去买酱油,但是小明接收到了通知,但是什么时候去买酱油就取决于小明,妈妈已经通知了,执行权也就在小明这里
那要怎么实现这些需求呢?
在JavaAPI中有许多声明抛出InterruptedException的方法(例如:wait() , join() , sleep()) , 这些方法在抛出InterruptedException 前 Java虚拟机会先将线程的中断标识符清除(置为false),这个时候就是程序接收到中断的信号,但是是否中断取决于程序的代码
接下来来看看代码演示:
public class ThreadDemo31 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello");
}
});
thread1.start();
Thread.sleep(10000);
System.out.println("interrupt前");
thread1.interrupt();
System.out.println("interrupt后");
}
}
我们观察到在调用 interrupt() , 后抛出一个InterruptedException ,然后程序继续执行
我们可以把这些操作分解: 其实就是调用interrupt(),将标识符置为中断状态,然后sleep中休眠中唤醒,唤醒的时候将中断标识符清除,然后再抛出异常,继续执行代码.
那么我们可以在异常这个地方做文章,可以在catch代码块中,直接中断线程,或者等待一段时间后再中断,或者不理会
这个就对应了,接到买酱油的通知后,立刻去买,等一段时间后再去买,不去买 !!!
🚗🚗🚗
下面我来使用代码更加清晰的看出声明了 InterruptedException 的方法对标识符的影响
public class ThreadDemo32 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
while (true) {
}
});
//设置为守护线程,进程结束后一起销毁
thread1.setDaemon(true);
thread2.setDaemon(true);
thread1.start();
thread2.start();
Thread.sleep(5000);//让线程充分执行
//通知线程中断
thread1.interrupt();
thread2.interrupt();
System.out.println(thread1.getName() + " " + thread1.isInterrupted());
System.out.println(thread2.getName() + " " + thread2.isInterrupted());
System.out.println();
Thread.sleep(10000);
}
}
🚁🚁🚁
我们发现Thread-0 有sleep方法,所以中断标识符就清除了,而Thread-1没有声明了InterruptedException的方法,所以中断标识符保留着
自定义Boolean变量中断线程:
在上文的中断线程中,是使用异常来进行交互的,除了使用标识符导致异常来中断线程外 **,还可以使用一个boolean变量来控制是否要中断线程 **
该程序使用一个静态布尔类型的属性,来控制while循环的条件,从而起到控制线程中断.这种交互方式是安全的,更适合来取消,中断线程执行,但是这种方式一旦启动就无法后悔,只能中断线程
public class ThreadDemo28 {
static boolean loop = true;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (loop) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
Thread.sleep(10000);
loop = false;
}
}
线程等待 / 通知机制:
在上文中, 我们有使用过 sleep() 来使代码的休眠 , 但是 sleep休眠代码还是有不少缺陷的.
sleep休眠只能来控制休眠时间,无法确保唤醒的及时性 >>
比如说:
我们想睡会觉后就起来上课,但是你不知道上课的时间,所以就无法准确的定闹钟喊醒,只能随意地睡觉,然后随缘起床,最后我们很难保证在上课的时候刚刚好起床,这样子就是无法保证及时性
但是现在有一个Java内置的等待 / 通知 机制可以很好的解决这个问题 , 这个方法存在于java.lang.Object 上
wait() && notify()
🚗🚗🚗
这个机制指的是:一个线程A通过 调用对象 O 的wait(),进入等待状态,这个时候另一个线程B调用notify()或者notifyAll()来对该线程进行通知,从而通知线程A返回,继续执行
这里的调用wait , notify的对象必须是同一个;
wait调用完的执行过程是,先释放锁,然后将线程进入等待,然后被通知结束等待的时候是要重新加锁
所以该机制都是与synchronized来配套使用的.锁对象与调用wait notify 的对象是必须同一个!
接下来代码来演示一下:
线程Thread1 调用wait(),然后这个时候线程状态为WAITING,然后就死等下去,直到Thread2线程去唤醒Thread1线程,如果这个时候Thread2线程没有去通知Thread1线程,那么这个时候Thread1线程就会进入死等状态,
public class ThreadDemo29 {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Thread thread1 = new Thread(() -> {
System.out.println("wait前");
synchronized (o) {
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("wait后");
});
Thread thread2 = new Thread(() -> {
System.out.println("notify前");
synchronized (o) {
o.notify();
}
System.out.println("notify后");
});
thread1.start();
Thread.sleep(1000);//为了thread先执行到等待,避免thread2先执行到notify
thread2.start();
}
}
wait() 细节:
💺💺💺
1. 使用wait() , notify() , notifyAll() 要先对调用对象加锁
public class ThreadDemo30 {
public static void main(String[] args) {
Object o = new Object();
Thread thread1 = new Thread(() -> {
System.out.println("wait前 ");
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait后");
});
Thread thread2 = new Thread(() -> {
System.out.println("notify前");
o.notify();
System.out.println("notify后");
});
thread1.start();
thread2.start();
}
}
没有调用对象加锁就使用 wait() / notify() ,会导致代码执行到wait(),notify()方法就会抛出一个非法锁异常
2.使用wait() 后 线程状态由RUNNABLE变为WAITING,并把当前线程放置等待队列中去
public class ThreadDemo30 {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Thread thread1 = new Thread(() -> {
System.out.println("wait前 ");
System.out.println(Thread.currentThread().getName() + " " + Thread.currentThread().getState());
try {
synchronized (o) {
o.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait后");
});
Thread thread2 = new Thread(() -> {
System.out.println("notify前");
System.out.println(thread1.getName() + " " + thread1.getState());
synchronized (o) {
o.notify();
}
System.out.println("notify后");
});
thread1.start();
Thread.sleep(1000);
thread2.start();
}
}
我们很明显地可以看到在没有调用wait()方法前,thread1的状态正在处于RUNNABLE,当调用wait()方法后,调用notify() 方法前,线程thread1的状态变为WAITING
3.notify() 或 notifyAll() 调用后,等待线程并不会直接从wait() 返回,而是需要等调用notify() 或 notifyAll()的锁释放后,才有机会继续拿到对象锁,从wait() 返回,从wait()方法返回的前提是获得了对象的锁
public class ThreadDemo30 {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Thread thread1 = new Thread(() -> {
System.out.println("wait前 ");
try {
synchronized (o) {
o.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait后");
System.out.println(Thread.currentThread().getName() + " " + Thread.currentThread().getState());
});
Thread thread2 = new Thread(() -> {
System.out.println("notify前");
synchronized (o) {
System.out.println(thread1.getName() + " " + thread1.getState());
o.notify();
System.out.println("调用notify");
System.out.println(thread1.getName() + " " + thread1.getState());
}
System.out.println("notify后");
});
thread1.start();
Thread.sleep(1000);
thread2.start();
}
}
🚂🚂🚂
我们可以看到,在线程1调用wait后,线程1处于WAITING状态,然后线程2调用notify通知线程1返回,这个时候线程1将从WAITING状态变为BLOCKED,因为这个时候线程1被唤醒,但是线程2的锁的对象锁还没有释放,所以线程1这个时候无法获取到对象锁,所以就会阻塞等待,然后最后的RUNNABLE状态就是线程2的锁释放了,被线程1拿到,所以等待线程才有机会从wait返回,最后继续执行,RUNNABLE状态
4.notify() 是将等待队列中的某一个线程移动到同步队列 从而唤醒, 而notifyAll是将等待队列中的所有线程都移动到同步队列中去, 被移动的线程的状态由 WAITING 变为BLOCKED
这个细节在第三个细节中已经解释了,可以移眼观看噢
🛫🛫🛫
从上述细节中可以看出,等待/通知 机制依托于同步机制,目的是为了确保等待线程从wait()方法返回时可以感知到通知线程对变量做出的修改
线程插队现象:
在我们生活中经常会有插队的现象,其实在线程中也是如此 :join() 方法
🛸🛸🛸
如果一个线程A执行了thread.join() , 表示当前线程A将等待thread线程执行终止后再从thread.join()返回,继续执行
join() 是Thread类的一个静态方法,Thread类除了提供join()之外,还提供了另外具有超时性质的join(long millis) 与 join(long millis,int nanos), 这两个方法表示,如果线程thread在规定时间内没有执行完毕,那么将会从超时方法中返回,继续执行线程A
接下来使用代码演示一遍:
public class ThreadDemo33 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(i + " " + Thread.currentThread().getName());
}
});
thread1.start();
for (int i = 0; i < 10; i++) {
if(i == 2){
thread1.join();
}
System.out.println(i + " " + Thread.currentThread().getName());
}
}
}
当线程main执行到了i == 2 的时候,就让thread1线程先执行,当thread1线程执行完毕后再来执行main线程