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同步块,其他线程仍然不能访问共享数据。
  1. wait会释放所有锁而sleep不会释放锁资源.
  2. wait只能在同步方法和同步块中使用,而sleep任何地方都可以.
  3. wait无需捕捉异常,而sleep需要.(都抛出InterruptedException ,wait也需要捕获异常)
  4. wait()无参数需要唤醒,线程状态WAITING;wait(1000L);到时间自己醒过来或者到时间之前被其他线程唤醒,状态和sleep都是TIME_WAITING
  5. 两者相同点:都会让渡CPU执行时间,等待再次调度

标签: none

评论已关闭