线程的通信

线程的其他方法

1. 终止线程

一般来说线程的执行完毕就会结束,无需手动关闭。但当我们需要手动杀死一个正在运行的线程。可以通过一些方法。

stop方法(废弃)

1
2
3
4
5
# stop()方法不推荐使用
因为stop()方法太暴力,会强制杀死线程。

这就类似于: 你正在写文档,突然停电,文档没有保存。
在程序运行过程中,强制杀死线程,线程还没有来得及保存上下文就结束了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Demo1 {

public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while(true){
System.out.println("i = " + i);
i++;
}
}
});

thread.start();
System.out.println("线程开始执行");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}

thread.stop();
System.out.println();
System.out.println("线程结束");
}
}

image-20210410174205954

2.中断线程

中断在计算机中是一个非常重要的机制。在多线程中,他是一种更好去中止线程的机制。我们可以通过与stop()做对比。stop()是强制杀死线程,不管当前的线程在做什么。而中断机制不会杀死线程,而是给线程发送一个通知,告诉线程,"你可以终止了"。之后,线程在接收到这个通知后如何处理,由线程自己确定。

isInterrupted()方法

1
2
isInterrupted()是一个实例方法,它通过检查中断标志位,判断当前线程是否被中断。
当前线程被中断时为true

interrupted()方法

1
interrupted()方法是一个静态方法,返回boolean类型,也是用来判断当前的线程是否被中断,但是同时会清楚当前线程的中断标志位

interrupt()方法

1
2
3
4
5
6
interrupt()方法是一个实例方法,他通知线程中断,设置中断标志位为true,中断标志位表示当前线程已经被中断了,它不会中断一个正在运行的进程。

如果线程被Object.wait(),Thread.join(),Thread.sleep()方法阻塞,然后调用interrupt()方法。此时线程在检查中断标识是如果是true,那么该线程将抛出一个InterruptedException中断异常,并且在抛出异常后立即将线程的中断标识位清除,即重新设置为false。该线程必须事先预备好处理这个异常,从而提早地终结被阻塞状态。


抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。
1
2
3
# 注意
synchronized在获得锁的过程中是不能被中断的。
调用interrupt()方法只是在目标线程中设置了中断标志位true。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//终止非阻塞线程,通过中断标志位判断中断线程
public class Demo2 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
System.out.println(Thread.currentThread().isInterrupted());
//判断中断标志位
while(!Thread.currentThread().isInterrupted()){
System.out.println("i = " + i);
i++;
}
System.out.println(Thread.currentThread().isInterrupted());
}
});

thread.start();
//在主线程睡0.01秒之后,将thread线程中断
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}

image-20210410195811396

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//终止一个阻塞线程
public class Demo3 {

public static void main(String[] args) throws InterruptedException {

Thread thread = new Thread() {
@Override
public void run(){
while(true) {
try {
sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
//设置中断标志位true
this.interrupt();
}

//当中断标志位为true时,退出循环
if(this.isInterrupted()){
break;
}

}
}
};
thread.start();
Thread.sleep(1000);
thread.interrupt();
}


}

image-20210410201531715

1
2
3
4
# 注意
在main方法中调用thread.interrupt()方法,此时thread线程内部的中断标志位会置为true
然后会触发run方法内部的InterruptedException异常,所以运行结果中由异常输出,当触发InterruptedException异常时,线程内部的中断标志又会被清除(false)。
因此我们在catch()中调用了this.interrupt();将中断标志位设置为true。然后退出循环。

3.等待(wait)、通知(notify)

等待wait()和通知notify()这两个方法并不是Thread类中的方法,而是每个Object类中定义的。每一个对象都有这两个方法。

1
2
3
public final native void wait(long timeout) throws InterruptedException;
public final native void notify();
public final native void notifyAll();
1
2
3
4
# 这两个方法只用于多线程之间的协作。

当在一个对象实例上调用wait()方法后,当前线程就会在这个对象上等待。
比如在线程1中调用object.wait()方法,那么线程1就会停止继续执行,转为等待状态。而当线程2调用object.notify()方法后,线程就会继续执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class waitNotifyDemo {

private static Object object = new Object();

public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread() {
@Override
public void run() {
System.out.println("线程1开始");
synchronized (object){
System.out.println("线程1得到锁");
try {
System.out.println("线程1等待并释放锁");
object.wait();
System.out.println("线程1等待结束并重新获得锁");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程1结束");
}
};

Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object){
System.out.println("线程2开始 得到锁");
try {
System.out.println("线程2睡眠5秒");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2通知线程1结束等待");
object.notify();
System.out.println("线程2释放锁");
}
System.out.println("线程2结束");
}
});

thread1.start();
//让主线程睡1秒,保证线程1先获得锁
Thread.sleep(1000);
thread2.start();
}
}

image-20210411093935130

1
2
3
4
5
# 对wait和notify的分析
这两个方法并不能随便使用。它必须包含在对应的synchronize语句中。
无论是wait方法还是notify方法都需要获取目标独享的一个监视器。这样做的目的是使得其他等待在object对象的线程不至于因为线程1的休眠而全部无法执行。
# wait和sleep方法的对比
wait方法可以被唤醒,wait会释放目标对象的锁
时间 线程1 线程2
T1 得到锁
T2 执行wait
T3 释放锁
T4 得到锁
T5 执行notify
T6 释放锁
T7 得到锁
T8
1
2
3
4
5
# 理解wait和notify
当一个线程调用了object.wait()方法时,那么他就会进出object对象的等待队列。在这个队列中,可能会有多个线程,因为系统可能运行多个线程等待某一个对象。当object.notify()方法被调用时,他就会从这个队列中随机选择一个线程,并将其唤醒。这个选择是不公平的,随机的,并不会先等待线程就会优先被选择。

# notifyAll()方法
这个方法与notify()方法类似。他会唤醒在这个等待队列中所有等待的线程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class ThreadDemo1 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 没有拿到锁");
synchronized (waitNotifyDemo2.class){
try {
System.out.println(Thread.currentThread().getName() + " 拿到锁");
System.out.println(Thread.currentThread().getName() + " 等待并释放锁");
waitNotifyDemo2.class.wait();
System.out.println(Thread.currentThread().getName() + " 重新得到锁");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 释放锁");
}
}
}

public class waitNotifyDemo2 {


public static void main(String[] args) throws InterruptedException {
ThreadDemo1 threadDemo = new ThreadDemo1();

Thread t1 = new Thread(threadDemo);
Thread t2 = new Thread(threadDemo);
Thread t3 = new Thread(threadDemo);
Thread t4 = new Thread(threadDemo);

t1.start();
//先让t1得到锁
Thread.sleep(1000);
//t2,t3,t4线程进入等待队列
t2.start();
t3.start();
t4.start();

//让t2,t3,t4有时间进入等待队列
Thread.sleep(2000);
new Thread(new Runnable() {
@Override
public void run() {
synchronized (waitNotifyDemo2.class){
//随机唤醒一个
waitNotifyDemo2.class.notify();
}
}
}).start();

synchronized (waitNotifyDemo2.class){
waitNotifyDemo2.class.notifyAll();
}
}

}

image-20210411101610676

4.挂起(suspend)和继续执行(resume)

在Thread类中,由两个方法,suspend()线程挂起、resume()线程继续执行。JDK中标注了这两个方法已经过时了,不推荐使用。因为suspend()方法去挂起线程不会释放任何锁资源,直到线程执行了resume方法。而且,对于被挂起的线程,从线程上的状态上看,依旧还是Runnable状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//resume()如果在suspend()方法前就被执行了,那么被挂起的线程可能很难有机会被继续执行了。
class ThreadDemo implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 挂起");
Thread.currentThread().suspend();
System.out.println(Thread.currentThread().getName() + " 继续执行");
}
}

public class suspendResumeDemo1 {

public static void main(String[] args) throws InterruptedException {
ThreadDemo threadDemo = new ThreadDemo();

Thread thread1 = new Thread(threadDemo);
Thread thread2 = new Thread(threadDemo);

thread1.start();
Thread.sleep(1000);
thread2.start();

thread1.resume();
thread2.resume();
}

}

image-20210411104919171

5.等待线程结束(join)和谦让(yeild)

有时候,一个线程可能需要等另一个线程结束以后才执行。JDK中提供了join()这个方法来操作。yeild()方法是一个静态方法,一旦执行,它会让出CPU,之后,他会继续和其他线程竞争CPU

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class joinYeildDemo {



public static void main(String[] args) throws InterruptedException {
Thread th = new Thread(new Runnable() {
@Override
public void run() {

try {
System.out.println("th线程睡眠5秒");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}

int i = 0;
for (;i<10;i++){
System.out.println("i = " + i);
}
}
});


th.start();
th.join();

System.out.println("等待th线程完成");
}
}
1
# join源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}

if (millis == 0) {
while (isAlive()) {
//将当前线程设置为等待态
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

线程的通信
https://johnjoyjzw.github.io/2020/10/05/线程的通信/
Author
John Joy
Posted on
October 5, 2020
Licensed under