在多线程机制中,线程之间需要传输信息。一般有以下几种通信机制:
- 共享对象:通过在共享对象中设置信号量,多个线程通过读取、修改该信号量来通信。
- wait/notify()方法:线程之间通过调用wait()、notify()方法实现线程等待、唤醒状态,从而达到线程通信的目的。
接下来我们分别看看这两种方法:
通过共享对象通信
在共享对象中设置信号量是最简单也是最常用的线程通信方法。共享变量需要使用volatile
修饰。我们知道,volatile
可以保证所修饰的变量立即被其他线程看到,而这种特性正是我们需要的。
下面是一个例子
1 | public class TestShared { |
以上例子应该很好理解,利用共享变量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 | public class WaitNotifyTest { |
当第一个线程运行到i=50
后,进入阻塞状态。直到第二个线程执行完毕,线程一才开始继续执行。
wait、notify机制要记得在执行该方法时,必须获得了该对象的锁。所以,为了使用方便,我们可以对其进行一定封装。
1 | public class WaitNotify2Test { |
NotifyObject
类即是上面示例的内部类。通过对wait、notify
的封装,我们可以使用更方便。
另外,还有一点需要注意:
不要在字符串常量或全局变量中调用wait、notify()方法。
这是因为,如果你的程序中有多套wait、notify线程,都使用一个字符串常量作为监视器的对象,则会出现假唤醒的情况。