java多线程之线程通信

在多线程机制中,线程之间需要传输信息。一般有以下几种通信机制:

  1. 共享对象:通过在共享对象中设置信号量,多个线程通过读取、修改该信号量来通信。
  2. wait/notify()方法:线程之间通过调用wait()、notify()方法实现线程等待、唤醒状态,从而达到线程通信的目的。

接下来我们分别看看这两种方法:

通过共享对象通信

在共享对象中设置信号量是最简单也是最常用的线程通信方法。共享变量需要使用volatile 修饰。我们知道,volatile 可以保证所修饰的变量立即被其他线程看到,而这种特性正是我们需要的。

下面是一个例子

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
public class TestShared {

private volatile boolean flag =false; //共享变量

//用来保证主线程等待运行线程结束后再退出
private CountDownLatch countDownLatch = new CountDownLatch(2);

private void test() {

new Thread(() -> {
for (int i=0;;i++) {
if (flag) { //当共享变量为true时,停止循环
break;
}
System.out.println(i+"-------->"+Thread.currentThread().getName());
}
countDownLatch.countDown();
},"thread-one").start();

}
private void test2(){

new Thread(() -> {
try {
Thread.sleep(3000);
flag=true; //3秒后将共享变量设为true
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();

},"thread-two").start();

}

public static void main(String[] args) {
TestShared testShared = new TestShared();
testShared.test();
testShared.test2();
try {
testShared.countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

以上例子应该很好理解,利用共享变量flag 控制thread-one线程的停止。即完成了线程之间的通信。

这里需要说明的是,你会发现,即使你的共享变量没有用volatile修饰,线程one也会在规定时间停止。是的。volatile的作用是保证修饰变量对其他线程的可见性。但这并不表明,你不用volatile修饰,其他线程就看不到这个变量了。你最终都会看到这个变化,只是volatile会让你立马看到修改的结果。

有关volatile的介绍,请参看我的另一篇文章java内存模型与volatile详解

wait、notify()方法

java内置了一个等待机制实现线程之间的通信,在Object对象中有wait()、notify()、及notifyAll()3个方法。

在一个线程中调用了某对象wait()后,该线程就将处于等待状态。直到另一个线程调用同一对象的notify()才能继续运行。这样我们就可以据此实现线程通信。

另外需特别注意的是:为了调用某对象的wait、notify方法,你需要获取这个对象的锁。这是必须的。在实现上,JVM会检查调用wait 的线程是否同时是锁的拥有者,否则就抛出异常。

同样,我们来看一个例子:

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 WaitNotifyTest {
/**
* wait、notify用法
* 当执行一个对象的wait()、notify()方法时,必须持有这个对象的锁,即在同步块中调用方法
* 在JVM实现中,当执行一个对象的wait()方法时,会首先检查它是否在同步块中,否则抛出异常
* @param args args
*/
public static void main(String[] args) {
NotifyObject notifyObject = new NotifyObject();

new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"开始");
for(int i=0;i<100;i++){
if(i==50){
//需获得这个对象的锁才能执行wait()方法
synchronized (notifyObject){
try {
notifyObject.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println( Thread.currentThread().getName()+":"+i);
}

}).start();
new Thread(()->{
for(int i=0;i<500;i++){
System.out.println( Thread.currentThread().getName()+":"+i);
}
synchronized (notifyObject){
notifyObject.notify();
}
}).start();
}
}
class NotifyObject {
}

当第一个线程运行到i=50后,进入阻塞状态。直到第二个线程执行完毕,线程一才开始继续执行。

wait、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
public class WaitNotify2Test {
private final NotifyObject notifyObject = new NotifyObject();

public void doWait() {
synchronized (notifyObject) {
try {
notifyObject.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public void doNotify() {
synchronized (notifyObject) {
notifyObject.notify();
}
}

public static void main(String[] args) {
WaitNotify2Test waitNotify2Test = new WaitNotify2Test();

new Thread(() -> {
for(int i=0;i<100;i++){
if(i==50){
waitNotify2Test.doWait();
}
System.out.println( Thread.currentThread().getName()+":"+i);
}
}).start();

new Thread(()->{
for(int i=0;i<500;i++){
System.out.println( Thread.currentThread().getName()+":"+i);
}
waitNotify2Test.doNotify();
}).start();
}
}

NotifyObject类即是上面示例的内部类。通过对wait、notify的封装,我们可以使用更方便。

另外,还有一点需要注意:

不要在字符串常量或全局变量中调用wait、notify()方法。

这是因为,如果你的程序中有多套wait、notify线程,都使用一个字符串常量作为监视器的对象,则会出现假唤醒的情况。

分享到:
0%