JAVA中用等待、通知机制优化循环等待

为什么要采用循环等待机制

之前的文章在解决死锁中的打破:占有且等待中的代码中,采用了死循环的方式来处理竞争失败的情况。

1
2
//返回true
while (!Allocator.getInstance().apply(this, target)) {}

当线程之间竞争不激烈,或者apply方法耗时较短时候这个方案还好,反之,可能有时需要循环几万次才能获取锁这种情况就会大量的浪费cpu计算资源。

java中的等待通知机制的使用

java中为我们提供了wait(),notify(),notifyAll()。方法配置synchronized完成通知等待。我们在不满足条件时候可以用lock.wait()来释放资源,进入等待状态,当满足提交过后,在调用notify()重新获取资源。

wait()的说明

wait是Object对象的方法,实际上是和“锁”对象对应使用的,

  1. wait必须在synchronized代码块中,否则会报异常:IllegalMonitorStateException
  2. wait()方法必须和synchronized对应起来,即synchronized(c){},那么一定是c.wait()

notify()和notifyAll()方法的使用注意事项

  • 当我们notfiy()之后,线程重新获取被保护资源。一定要在次判断锁条件,因为notify之后到资源重新被获取,这段时间有可能再次不满足条件。
  • 没有特殊情况尽量用notifyAll()方法。因为notify只会“唤醒”一个线程,有可能这个线程不是需要唤醒的线程导致执行效率低下。而notfiyAll会唤醒所有在等待的线程,并且同时竞争资源,保证会唤醒需要的唤醒的线程。

之前的代码改造如下:

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
public class Allocator {
private static Allocator instance = new Allocator();
public static Allocator getInstance() {
return instance;
}

private Allocator() {
}

private Set<Account> set = new HashSet<>();
public synchronized boolean apply(Account from, Account to) {

while (set.contains(from) || set.contains(to)) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

set.add(from);
set.add(to);
return true;
}

public synchronized void free(Account from, Account to) {
set.remove(from);
set.remove(to);
this.notifyAll();
}
}

public class Account {
private int balance;

public void transfer(Account target, int amt) {
try {
Allocator.getInstance().apply(this, target);

synchronized (this) {
synchronized (target) {
if (balance >= amt) {
balance -= amt;
target.setBalance(target.getBalance() + amt);
}
}
}

} finally {
Allocator.getInstance().free(this, target);
}
}
}

思考

wait()和sleep()的区别

wait

  1. wait是Object中的方法,和锁对应;wait要在Synchronized代码块中执行
  2. wait会释放被锁资源。

sleep

  1. sleep是Thread中的静态方法,让当前线程休眠一段时间
  2. sleep是让出当前线程的执行权限,让优先级低的线程执行,但是不会释放锁资源,也就是说其他线程遇到synchronized代码快会阻塞住

wait的原理

wait方法被调用后,当前线程会去掉锁标记,并且会将当前线程对象放入到对象资源池,知道notify或者notifyAll被调用。
当notify或者notfiyAll被调用时候,会将对象资源池中的一个线程对象或者所有线程移入到锁标记资源池,在锁标记资源池的线程对象会争抢锁,获取资源,获取到资源的开始执行

wait有哪些被唤醒的方法

lock.notify、lock.notifyAll

wait(1000L),时间到后

thread.interrupt 别的线程调用线程的interrupt方法