wait notify 深入理解

wait 和 notify 是object类的方法 ,也就是说在java世界里,所有的class对象都会持有这2个方法。
在使用过程中,wait 和 notify一定是成对出现的。

在调用wait时候,线程必须持有被调用对象的锁。当wait被调用,线程就会释放持有的对象锁 。 sleep方法并不会释放锁。

关于wait 和 notify方法的总结:

  1. 调用wait时候,首先要保证线程是有了对象的锁
  2. 调用了wait后,线程释放当前对象锁,并且进入等待
  3. 进入等待状态后,会等待其他线程调用 notify或者notifyAll 进行唤醒
  4. 当线程被唤醒后,就会和线程一起竞争对象锁(公平竞争),只有当线程获取到锁,才会继续执行
  5. 调用wait的代码需要放在 synchronize 方法或者 synchronize代码块中,保证代码在调用wait时候,已经获取锁。
  6. 当调用notify方法时候,会随机唤醒正在等待唤醒线程集合中的一个,一旦获取到锁,才会继续往下执行
  7. 在某一时刻,只会有一个线程会持有对对象锁

需求: 使用线程按照101010…..这种顺序打印

下面的代码实现中,利用 wait和 notify ,在2线程中分别进行数据的加和减

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public class Test01 {

public static void main(String[] args) {

Bucket bucket = new Bucket();

new Thread(new Product(bucket) , "product - 1").start();
new Thread(new Consumer(bucket), "consumer - 1").start();
}
}

class Bucket {

private int count = 0 ;

public synchronized void getCount() {
if (count != 1) {
try {
System.out.println("线程: " + Thread.currentThread().getName() + "进入等待 ");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println("线程: " + Thread.currentThread().getName() + " count = " + count);
notify();
}


public synchronized void setCount() {
if (count!= 0) {
//wait
try {
System.out.println("线程: " + Thread.currentThread().getName() + "进入等待 ");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

count++;
System.out.println("线程: " + Thread.currentThread().getName() + " count = " + count);
notify();
}
}

class Consumer implements Runnable {

private final Bucket bucket;

public Consumer(Bucket bucket) {
this.bucket = bucket;
}

@Override
public void run() {
while (true) {
try {
Thread.sleep(new Random().nextInt(1000) + 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
bucket.getCount();
}
}
}

class Product implements Runnable {


private final Bucket bucket;

public Product(Bucket bucket) {
this.bucket = bucket;
}

@Override
public void run() {
while (true) {
try {
Thread.sleep(new Random().nextInt(1000) + 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
bucket.setCount();
}
}
}

执行结果,如预期 :

线程:  consumer - 1进入等待 
线程:  product - 1 count = 1
线程:  consumer - 1 count = 0
线程:  product - 1 count = 1
线程:  consumer - 1 count = 0
线程:  product - 1 count = 1

下面我们对程序做一次改造 ,在main方法里面 各自使用2个线程进行操作。

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {

Bucket bucket = new Bucket();

new Thread(new Product(bucket) , "product - 1").start();
new Thread(new Product(bucket) , "product - 2").start();


new Thread(new Consumer(bucket), "consumer - 1").start();
new Thread(new Consumer(bucket), "consumer - 2").start();
}

执行结果:

线程:  product - 2 count = 1
线程:  consumer - 2 count = 0
线程:  consumer - 2进入等待 
线程:  consumer - 1进入等待 
线程:  product - 1 count = 1
线程:  consumer - 2 count = 0
线程:  consumer - 1 count = -1
线程:  product - 2进入等待 
线程:  product - 1进入等待 
线程:  consumer - 2进入等待 
线程:  consumer - 1进入等待 
......
线程进入了挂起状态,也不再打印结果

在使用了多个线程以后,我们的程序执行结果失控了 。。。
下面我们对2种情况进行分析 ,首先是单个线程:

在Bucket中 ,我们调用了wait和notify ,因为只有2个线程,所以当我们其中一个线程进入wait,另外一个线程一定会被唤醒 ,所以 product-1 和 consum-1 有序的进行增加和减少,保证了我们的运行结果 。

在多个product和consume线程的模式下,

  1. 担当product-1获取到锁,如果满足 count != 0 那么进入wait,唤醒其他等待队列中的任意一个线程。
  2. 假如product-2被唤醒,这时候也满足了 count!=0 这个条件,product-2也进入wait
  3. 这时候等待队列中的任意一个被欢迎,如果是product-1 那么继续往下执行,count++,并唤醒等待队列中的线程
  4. product-2被唤醒,也执行 count++ ,那么就出现了我们上面失控的结果

解决方案:

1
2
3
4
5
public synchronized void getCount() {
if (count != 1) {

public synchronized void getCount() {
while (count != 1) {

将 if 修改为while , 让程序获取到锁以后,再次检查count的状态即可