Java线程间通信
周sir |
2015年4月15日 |
多线程编程 |
0 条评论 | 1613
同步方法能挡住其他线程同时进同一个对象,但还不够精巧。生产者一边造数据、消费者一边用,还要互相等对方干完——如果靠轮询死循环盯条件,会浪费大量 CPU。
Java 用 wait()、notify()、notifyAll() 做线程间通信。它们是 Object 的 final 方法,必须在 synchronized 方法(或块)里调用。
| 方法 |
作用 |
| wait() |
放弃管程进入睡眠,直到同对象上有人 notify;另有可限时的重载 |
| notify() |
唤醒同一对象上某个正在 wait 的线程 |
| notifyAll() |
唤醒全部等待线程,优先级高的先跑 |
一、错误写法:同步了仍会超车
四个类:Q 是队列,Producer / Consumer 是线程,PC 把它们串起来。put / get 虽然 synchronized,但没有“有没有新数据”的约定,生产者可以连写,消费者可以连读同一个值。
class Q {
int n;
synchronized int get() {
System.out.println("Got: " + n);
return n;
}
synchronized void put(int n) {
this.n = n;
System.out.println("Put: " + n);
}
}
class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this, "Producer").start();
}
public void run() {
int i = 0;
while (true) { q.put(i++); }
}
}
class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, "Consumer").start();
}
public void run() {
while (true) { q.get(); }
}
}
class PC {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
System.out.println("Press Control-C to stop.");
}
}
可能出现:Put: 1 之后 Got: 1 连打五次,然后 Put: 2 到 7 消费者根本来不及拿。输出随 CPU 和负载变化。
二、正确写法:wait / notify 对向握手
用 valueSet 标记格子里有没有新值。get 发现没有就 wait,取走后 notify 生产者;put 发现还没被取走就 wait,放入后 notify 消费者。条件用 while 而不是 if,避免虚假唤醒后状态已变仍往下走。(原文正确示例括号错位,这里按语义修好。)
class Q {
int n;
boolean valueSet = false;
synchronized int get() {
while (!valueSet) {
try { wait(); } catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}
}
System.out.println("Got: " + n);
valueSet = false;
notify();
return n;
}
synchronized void put(int n) {
while (valueSet) {
try { wait(); } catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}
}
this.n = n;
valueSet = true;
System.out.println("Put: " + n);
notify();
}
}
class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this, "Producer").start();
}
public void run() {
int i = 0;
while (true) { q.put(i++); }
}
}
class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, "Consumer").start();
}
public void run() {
while (true) { q.get(); }
}
}
class PCFixed {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
System.out.println("Press Control-C to stop.");
}
}
正确输出成对出现:Put: 1 / Got: 1 / Put: 2 / Got: 2 … 生产者不会超车,消费者也不会把同一个数拿两遍。
一句话总结:synchronized 只能互斥,不能表达“等到有数据”;生产者消费者要在同步方法里 wait / notify 对向握手,条件用 while 判断。
转载请注明来源:Java线程间通信
我是周sir,这是我的博客。致力于分享我学会的技术和优秀的文章。微信公众号 “周sir专栏” 欢迎大家关注。