针对多个不同线程之间操作同一个数据的情况,被称为线程之间的通信.
而这个操作不但要考虑到线程的同步问题还有一个隐藏的问题
如果这些操作之间是有一定的顺序的,比如 A-->B-->C
那么就不应该是谁获得了执行权就执行谁,这时候java为我们提供了等待唤醒机制

问题描叙

比如某个车行轮流生产宝马与奔驰两种汽车,然后消费者购买生产的汽车
简单案例 :
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
51
52
53
54
55
56
57
58
59
60
61
62
63
public static void main(String[] args) {
Car s = new Car();
Product product = new Product(s);
Consumer consumer = new Consumer(s);
Thread product1 = new Thread(product,"车行1");
Thread consumer1 = new Thread(consumer,"消费者1");
product1.start();
consumer1.start();
}
class Car {
private String name;
private int money;
public synchronized void set(String name,int money){
this.name = name;
this.money = money;
}
public synchronized Object[] get(){
Object[] carInformation = {new StringBuilder().append(name).toString(),money};
return carInformation;
}
}
class Product implements Runnable {
private Car s;
public Product() {}
public Product(Car s) {this.s = s;}
int x=0;
@Override
public void run() {
while (true) {
if (x % 2 == 0) {
s.set("宝马",30);
System.out.println(Thread.currentThread().getName()+" 已生产 : 宝马,售价(万) : 30");
} else {
s.set("奔驰",31);
System.out.println(Thread.currentThread().getName()+" 已生产 : 奔驰,售价(万) : 31");
}
x++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
private Car s;
public Consumer(){}
public Consumer(Car s) {this.s = s;}
@Override
public void run() {
while (true) {
Object[] carInformation = s.get();
System.out.println(Thread.currentThread().getName()+" 购买:"
+carInformation[0] + "---" + carInformation[1]);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
其执行结果如下 :

问题分析

其实上述代码存在一个问题,那就是消费者不一定会在指定的时间来消费产品
正常的情况应该是,先把汽车生产出来,等被消费完了就再生产一个
而不是上面这种指定时间的情况
假如车行最快10ms就能生产一辆车,而处于旺季,那么就应该以最快的速度生产
并且消费者即使想要购买,也要等车行生产出来才行
而在淡季假如消费者1000ms才买一辆车,那么就不应该是100ms生产一辆

等待唤醒机制

根据分析,可以知道.多个线程的操作之间需要一个传呼的工具
也就是,消费者需要知道有东西可以消费了,执行;否则等待
生产者知道东西被消费了,执行;否则等待
这时候使用共享数据是最合适的,只需要在共享数据内定义标记信息就可以完成
由于数据的类型根据不同的场景必定不同,故而java在Object类中定义了相关方法

与之相关的方法

在调用等待方法的时候,会释放掉当前的锁对象,故而等待唤醒代码可以放到同步代码块里面
1
2
3
4
5
6
7
8
9
10
//在调用在其他线程调用此对象的notify()方法或notifyAll()方法前,当前线程一直等待。
public final void wait() throws InterruptedException
//在其他线程调用此对象的notify()方法或notifyAll()方法,或者超过指定的时间量前,当前线程等待。时间为0则一直等待
public final void wait(long timeout) throws InterruptedException
//和设置时间一样,nanos表示额外时间,一微秒为单位、范围是 0-999999
public final void wait(long timeout, int nanos) throws InterruptedException
//唤醒在此对象监视器上等待的单个线程,如果多个在等待随机唤醒一个
public final void notify()
//唤醒此对象的所有等待线程
public final void notifyAll()
对上述代码的改进 :
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
class Car {
..
private boolean flag = false;
public synchronized void set(String name,int money){
if (flag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.money = money;
flag = true;
notify();
}
public synchronized Object[] get(){
if (!flag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object[] carInformation = {new StringBuilder().append(name).toString(),money};
flag = false;
notify();
return carInformation;
}
}
执行结果如下 :