Java线程间通信

总结下Java线程间通信的编程技巧。

  • Java中线程处于Runnable状态

    1. 调用sleep方法超过指定时间
    2. 线程调用的阻塞I/O返回
    3. 线程成功获得试图同步的监视器
    4. 线程在等待某个通知,其他线程发出通知
    5. 处于挂起状态调用了resume恢复方法
  • Java中线程处于阻塞状态

    1. 线程调用sleep方法
    2. 线程调用阻塞式I/O,调用返回
    3. 线程试图获取一个同步监视器,但该同步监视器正在被其他线程使用
    4. 线程等他某个通知
    5. 程序调用suspend方法将线程挂起,但谨慎调用该方法。

线程间通信促进资源的共享,加大线程的交互性。

不使用等待/通知机制的线程间通信

不使用等待/通知机制的进行线程间通信可以使用轮询。例子如下:

原理:每个线程都在while循环下轮询共享变量,根据共享变量的值判断下一步的执行。

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
import java.util.Date;

public class ITCTest {
private static boolean status = true;

private static class Thread1 extends Thread {
public void run() {
while (status) {
System.out.println("time is " + new Date());
}
}
}

public static class Thread2 extends Thread {
public void run() {
while (status) {
System.out.println("time is " + new Date());
}
}
}

public static void main(String[] args) {
Thread t1 = new Thread1();
Thread t2 = new Thread2();

t1.start();
t2.start();

delay();
status = false;
}
}

如果线程轮询时间过小,消耗更大的CPU资源,如果轮询时间过大,则可能错过数据的更新。

等待/通知机制 wait/notify

wait:暂停线程的等待,调用该方法的线程释放共享资源的锁,运行态转换为就绪态,加入等待队列。wait方法可以传入参数,指定等待时间。
notify:通知线程继续运行,随机唤醒等待队列中的一个线程。

注意:调用wait方法会释放锁,但调用notify方法并不释放锁,只有当notify所在的synchronized的代码块被执行完毕后才被调用。

例子: (生产者/消费者模型在代码库中)

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

public class Task1 extends Thread {
private Object lock;
public Task1(Object lock) {
super();
this.lock = lock;
}

@Override
public void run() {
try {
synchronized (lock) {
System.out.println("A");
lock.wait();
System.out.println("C");
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public class Task2 extends Thread {
private Object lock;
public Task2(Object lock) {
super();
this.lock = lock;
}

@Override
public void run() {
synchronized (lock) {
System.out.println("B");
lock.notify();
System.out.println("D");
}
}
}

public class Test {
public static void main(String[] args) {
Object lock = new Object();
Thread t1 = new Task1(lock);
Thread t2 = new Task2(lock);

t1.start();
t2.start();
}
}

注意:

  • 当线程在wait()状态下,调用interrupt()方法会出现InterruptedException异常。

通过管道进行线程通信

  • 字节流
  • 字符流

这部分参考IO模块的实现

join方法的使用

阻塞当前调用join的线程,直到被阻塞的线程返回。可以传入long类型的参数指定timeout参数。

  • join 方法的内部实现采用 wait
  • sleep 方法调用后不释放锁

ThreadLocal

ThreadLocal线程本地:每个线程都有同一个变量的独有拷贝。拥有get、set接口。

实现原理

有一个Map维护线程对象和值的映射。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

public class ThreadLocalBasic {
static ThreadLocal<Integer> local = new ThreadLocal<>();

public static void main(String[] args) throws InterruptedException {
Thread child = new Thread() {
@Override
public void run() {
System.out.println("child thread initial: " + local.get());
local.set(200);
System.out.println("child thread final: " + local.get());
}
};

local.set(100);
child.start();
child.join();
System.out.println("main thread final: " + local.get());
}
}

以上。