06 | 用“等待-通知”机制优化循环等待
public class Allocator {
private final List<Account> als = new LinkedList<Account>();
// 一次性申请所有资源
public synchronized void apply(Account from, Account to) {
//一开始也是觉得while条件写反了,后来理解了。Allocator 是管理员分配者,而不是申请者
// 经典写法
while (als.contains(from) || als.contains(to)) {
try {
System.out.println("等待用户 -> " + from.getId() + "_" + to.getId());
wait();
} catch (Exception e) {
//notify + notifyAll 不会来这里
System.out.println("异常用户 -> " + from.getId() + "_" + to.getId());
e.printStackTrace();
}
}
als.add(from);
als.add(to);
}
// 归还资源
public synchronized void free(Account from, Account to) {
System.out.println("唤醒用户 -> " + from.getId() + "_" + to.getId());
als.remove(from);
als.remove(to);
notifyAll();
}
}
public class Account {
// actr 应该为单例
private final Allocator actr;
//唯一账号
private final long id;
//余额
private int balance;
public Account(Allocator actr, long id, int balance) {
this.actr = actr;
this.id = id;
this.balance = balance;
}
// 转账
public void transfer(Account target, int amt) {
// 一次性申请转出账户和转入账户,直到成功
actr.apply(this, target);
try {
//TODO 有了资源管理器,这里的synchronized锁就不需要了吧?!
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
//模拟数据库操作时间
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
actr.free(this, target);
}
}
public long getId() {
return id;
}
}
文章中的左边和右边的两个队列应该改一改名字,不应该都叫等待队列,这样对新手很容易产生误解。如果左边的叫做同步队列,右边的叫做等待队列可能更好。左边的队列是用来争夺锁的,右边的队列是等待队列,是必须被notify的,当被notify之后,就会被放入左边的队列去争夺锁
尽量使用 notifyAll()
在上面的代码中,我用的是 notifyAll() 来实现通知机制,为什么不使用 notify() 呢?这二者是有区别的,
- notify() 是会随机地通知等待队列中的一个线程
- notifyAll() 会通知等待队列中的所有线程。
从感觉上来讲,应该是 notify() 更好一些,因为即便通知所有线程,也只有一个线程能够进入临界区。但那所谓的感觉往往都蕴藏着风险,实际上使用 notify() 也很有风险,它的风险在于可能导致某些线程永远不会被通知到。假设我们有资源 A、B、C、D,线程 1 申请到了 AB,线程 2 申请到了 CD,此时线程 3 申请 AB,会进入等待队列(AB 分配给线程 1,线程 3 要求的条件不满足),线程 4 申请 CD 也会进入等待队列。我们再假设之后线程 1 归还了资源 AB,如果使用 notify() 来通知等待队列中的线程,有可能被通知的是线程 4,但线程 4 申请的是 CD,所以此时线程 4 还是会继续等待,而真正该唤醒的线程 3 就再也没有机会被唤醒了。所以除非经过深思熟虑,否则尽量使用 notifyAll()。
wait()和sleep区别
- wait()方法与sleep()方法的不同之处在于,wait()方法会释放对象的“锁标志”。当调用某一对象的wait()方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了notify()方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的notifyAll()方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。
- sleep()方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放“锁标志”,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据。
- wait会释放所有锁而sleep不会释放锁资源.
- wait只能在同步方法和同步块中使用,而sleep任何地方都可以.
- wait无需捕捉异常,而sleep需要.(都抛出InterruptedException ,wait也需要捕获异常)
- wait()无参数需要唤醒,线程状态WAITING;wait(1000L);到时间自己醒过来或者到时间之前被其他线程唤醒,状态和sleep都是TIME_WAITING
- 两者相同点:都会让渡CPU执行时间,等待再次调度!
评论已关闭