线程间通信

一个线程启动别的线程

       new Thread().start()、 Executor.execute() 等。

一个线程终结另一个线程

Thread.stop()

       现在被弃用。它会让线程立即掐断,不管对方在做什么。

Thread.interrupt()

       温和式终结,不立即、不强制,只是标记为中断。isInterrupted() 只做中断状态的检查,而 Thread.interrupted() 做检查并重置中断状态(设置为 false)。一般会使用 Thread.interrupted()。如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void runTest() {
Thread thread = new Thread() {
@Override
public void run() {
for (int i = 0; i < 1_000_000; i++) {
if(Thread.interrupted()){
//收尾
return;
}
System.out.println("number: " + i);
}
}
};
thread.start();
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
thread.interrupt();
}

       一般在耗时事件前面做 Thread.interrupted(),如处理图片之前等。

       如下代码:

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
public void runTest() {
Thread thread = new Thread() {
@Override
public void run() {
for (int i = 0; i < 1_000_000; i++) {
if(Thread.interrupted()){
//收尾
return;
}
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
//收尾
return;
}
System.out.println("number: " + i);
}
}
};
thread.start();
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
thread.interrupt();
}

       thread.interrupt() 终止线程时,线程正处于等待(睡眠)状态(代码中主线程等待时间小于子线程等待时间,500<2000),则子线程中抛出 InterruptedException,如果要支持从外部关闭线程,需要在子线程的 catch 方法内操作。如果不处理,则子线程的循环继续执行。InterruptedException 也会重置线程的中断状态。如果在线程等待时中断,或者在中断状态下等待,即先调用“thread.interrupt()”然后再调用“Thread.sleep()”,则直接结束等待过程,因为等待过程什么也不会做,而 interrupt() 的目的是让线程做完收尾工作后尽快终结,所以要跳过等待过程。

       如果不打算从外部终结线程,那么 InterruptedException 异常可以不处理。“Thread.sleep()”方法要抛出异常,是为了能在睡眠中被打断。

       在 Android 中,有如下方法:

1
SystemClock.sleep();

       它和“Thread.sleep()”是一样的,只是它把异常“吞掉”了。

Object.wait() 和 Object.notify() / notifyAll()

       如下代码,两个线程,500ms后打印字符串,1000ms后初始化字符串:

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
public class WaitDemo implements TestDemo {
private String sharedString;

private synchronized void initString() {
sharedString = "wy521angel";
}

private synchronized void printString() {
System.out.println("String: " + sharedString);
}

@Override
public void runTest() {
final Thread thread1 = new Thread() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
printString();
}
};
thread1.start();
Thread thread2 = new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
initString();
}
};
thread2.start();
}
}

       输出结果为 null,将 thread1 的睡眠时间修改为2000ms,则可以打印出结果,但现实中耗时的方法可能是无法知道具体执行结束时间的。

       使用 Object.wait() 和 Object.notify() / notifyAll() 方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class WaitDemo implements TestDemo {
private String sharedString;

private synchronized void initString() {
sharedString = "wy521angel";
notifyAll();
}

private synchronized void printString() {
while (sharedString == null){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 等待队列中的线程被唤醒后从这里开始继续执行
}
System.out.println("String: " + sharedString);
}
...
}

       当线程A访问 printString() 方法时,拿到 Monitor,当它发现 sharedString 为 null 时,执行 wait() 方法,将 Monitor 释放,以便后面的线程可以进入 initString() 执行初始化操作。该线程进入等待队列,等待别的线程唤醒自己,注意此处的队列并不是排队获取 Monitor 的队列。

此处增加排队获取 Monitor 的队列的链接。

       当线程B、线程C访问 printString() 方法时,由于 sharedString 为 null 同样进入等待被唤醒的队列。线程D访问 initString() 方法,并且拿到了 Monitor,执行了初始化操作,并执行 notifyAll() 方法,通知之前被等待唤醒的线程A、线程B和线程C。notifyAll() 方法是唤醒所有的等待队列中的线程,而 notify() 只唤醒一个。notifyAll() 方法更常用。

1
等待队列中的线程被唤醒后,从 wait() 方法之后,更确切地说是从 catch 异常快结束后的代码开始执行。

       注意代码中的“while (sharedString == null)”不能修改成“if (sharedString == null)”,wait() 并不能确定是被谁唤醒的,类似 interrupt() 方法不仅可以唤醒 sleep() 也可以唤醒 wait(),抛出 InterruptedException 异常,如果是 if,则代码会继续执行“System.out.println(“String: “ + sharedString);”方法。事实上下面代码块是使用 wait() 非常标准的一种方式:

1
2
3
4
5
6
7
while (判断条件){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

       wait() 和 notify() / notifyAll() 都需要放在同步代码块(synchronized 修饰的方法或者代码块)里面。

Thread.join()

       让另一个线程插在自己前面。如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
Thread thread2 = new Thread() {
@Override
public void run() {
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
initString();
}
};
...

       表示 thread2 线程希望 thread1 插队,在对方完全执行结束之后自己再继续之前的执行。

Thread.yield()

       暂时让出自己的时间片给同优先级的线程。如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
Thread thread2 = new Thread() {
@Override
public void run() {
Thread.yield();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
initString();
}
};
...

参考资料:
腾讯课堂 HenCoder

Fork me on GitHub