线程通信之wait()-notify()
线程通信的目标是使线程间能够互相发送信号。另一方面,线程通信使线程能够等待其他线程的信号。
例如,线程B可以等待线程A的一个信号,这个信号会通知线程B数据已经准备好了。
Object
提供的wait()
和notify()
wait,notify,notifyAll
是定义在Object类的实例方法,用于控制线程状态。
-
wait()
方法 表示持有对象锁的线程A准备释放对象锁权限,释放CPU
资源并进入等待。 -
notify()
表示持有对象锁的线程A准备释放对象锁权限,通知JVM
唤醒某个竞争该对象锁的线程X。线程Asynchronized
代码作用域结束后,线程X直接获得对象锁权限,其他竞争线程继续等待(即使线程X同步完毕,释放对象锁,其他竞争线程仍然等待,直至有新的notify() ,notifyAll()
被调用)。 -
notifyAll()
表示持有对象锁的线程A准备释放对象锁权限,通知JVM
唤醒所有竞争该对象锁的线程,线程Asynchronized
代码作用域结束后,JVM
通过算法将对象锁权限指派给某个线程X,所有被唤醒的线程不再等待。线程Xsynchronized
代码作用域结束后,之前所有被唤醒的线程都有可能获得该对象锁权限,这个由JVM
算法决定。
通俗比喻:有两个人A和X都要和一个女孩G约会(A线程和X线程都调用
synchronized
约会方法,锁对象G.class
)。A率先和G约会了(A线程进入
synchronized
方法),X只能等着。A在约会途中被电话叫走,把G凉在那里(调用
G.class.wait()
)。这时候X发现G没人约会了,于是上场(X线程进入
synchronized
方法)。等到X和G约会完成,X又打电话叫A回来继续约会(X线程调用
G.class.notify()
方法),A才回来继续约会直到完成。
notifyAll()
就是不止A和X了,可能有更多的人要和G约会,等A走了之后某个人上场。
下面用一个案例来更好的了解这两个方法的使用和机制。
案例:控制台随机输入一个正整数n,通过两个线程分别打印n+1、n+2、n+3、n+4、n+5、n+6,要求线程交替打印(即一个奇数线程,一个偶数线程),n的初始指为-1。
先建一个奇偶数打印的类
public class ParityPrint {
/** 初始化-1 */
private int i = -1;
}
再分别创建奇数线程和偶数线程
- 这里两个线程都用同一个锁
ParityPrint.class
,这样两个线程就不会同时打印了。 -
(parityPrint.i & 1) == 1
判断奇偶用位运算,最后一位0是偶数,1是奇数。 -
i
的初始值是-1,等到控制台输入才能继续,所以两个线程刚开始会交替运行到ParityPrint.class.wait()
这里,然后两个线程会进入wait
状态,全部释放锁。
跑一下
根据控制台打印可知两个线程都跑到wait()
,但是没有再跑下去。
控制台输入,唤醒两个线程
这里main
方法用notifyAll
来唤醒了两个线程,结果符合预期。
这里我把i
改成了volatile
修饰,因为如果不修饰可能会出现一个问题:输入奇数,当偶数线程被唤醒将i
改成了偶数,但是还没有刷新到主内存,这时候奇数线程获得锁,发现i
还是还是奇数(主内存是偶数了,但是还没同步倒线程栈内存),又重新进入wait()
方法,导致两个线程都进入wait状态。
思考
-
main
方法用notify()
唤醒一个线程行不行?可以,但是如果输入一个奇数,又只唤醒了奇数线程,那奇数线程又回到wait状态。
-
为什么日志里主线程唤醒所有线程后,奇数线程获得了锁,但是又回到了等待中?
notifyAll
后奇数线程率先获得锁,执行while
条件发现目前输入的是奇数,所以又重新wait
放弃了锁。 -
parityPrint.i < 0 || (parityPrint.i & 1) == 0
用if
判断行不行?不行,因为当输入奇数,主线程唤醒两个线程,如果奇数线程优先获得锁,就会直接往下打印一个偶数。
代码可以在Github上找到。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。