Java 线程通知与等待

Java 中的 java.lang.Object 类是所有类的父类.
Java 把所有类都需要的方法放到了 Object 类里面, Object 类里的非private的方法有:

  1. getClass
  2. hashCode
  3. equals
  4. clone
  5. toString
  6. notify 系列(包含 notify, notifyAll)
  7. wait 系列
  8. finalize

Java 线程的 “通知-等待” 就是通过 notify 系列 和 wait 系列 的方法实现的.

1 wait 系列方法

1.1 wait(long timeout)

public final native void wait(long timeout) throws InterruptedException;

下面是 wait(long timeout) 方法的注释:
导致当前线程 等待 直到其他线程调用了 对象(就是 共享变量)的 notify 系列方法, 或者已经过了指定时间.
当前线程必须持有 对象的监视器(monitor) 才能调用 wait 方法.
wait 方法 导致当前线程(称为T)将自身置于 此对象的等待集(wait set)中,然后放弃在此对象上的所有同步声明(synchronization claims). 线程T 因 线程调度 而被禁用, 并处于休眠状态(dormant), 直到发生以下四种情况之一:

  1. 其他线程调用 对象的 notify方法, 而 线程T 恰好被(任意)选择为要唤醒的线程.
  2. 其他线程调用 对象的 notifyAll方法.
  3. 其他线程 中断(java.lang.Thread#interrupt) 了 线程T.
  4. 指定的时间已过, 但如果超时为零, 则表示无限等待时间.

然后从 该对象 的等待集(wait set)中删除 线程T, 并重新启用线程调度(thread scheduling).
然后, 线程T 以普通的方式(usual manner) 与 其他线程 竞争在对象上同步的权利; 一旦它获得了对象的控制权, 线程T 对 对象的所有同步声明都恢复到原来的状态, 也就是说, 恢复到调用 wait 方法时的状态.
线程T 然后从 wait 方法的调用返回. 因此, 从 wait 方法返回时, 对象和线程T 的同步状态(synchronization state) 与调用 wait 方法时完全相同.
线程也可以在不被通知, 中断 或 超时 的情况下唤醒, 即所谓的 虚假唤醒(spurious wakeup). 虽然这种情况在实践中很少发生, 但应用程序必须通过 测试导致线程被唤醒的条件 来防范这种情况, 并在条件不满足时继续等待. 换句话说, 等待应该总是在循环中发生, 如下所示:

synchronized (obj) {
   while (<condition does not hold>)
       obj.wait(timeout);
       // Perform action appropriate to condition ...
}

注意, 调用共享变量的 wait方法后, 在将 当前线程 放入该对象的 等待集(wait set)中时, 只释放该共享变量上的监视器锁; 如果当前线程还持有其他共享变量的锁, 则这些锁不会被释放.

wait 只能 被 持有对象监视器 的线程调用. 所以 wait 总是卸载 synchronized 块中.

1.2 wait(long timeout, int nanos)

这个方法和 一个参数的 wait 方法类似, 只是它允许更精细(纳秒级)地控制在放弃之前等待通知的时间.

public final void wait(long timeout, int nanos) throws InterruptedException 

1.3 wait()

public final void wait() throws InterruptedException {
        wait(0);
    }

无参 wait() 调用了 wait(0) , 表示无限等待时间. 功能和一个参数的 wait 方法一模一样.

2 notify 系列方法

2.1 notify()

public final native void notify();

下面是 notify() 方法的注释:
唤醒(wake up)正在 该对象(就是 共享变量) 监视器上 等待(wait) 的单个线程. 如果有多个线程正在等待此对象, 则选择其中一个线程进行唤醒, 选择是任意的. 线程通过调用一个 wait 方法在对象的监视器上 等待.
在当前线程放弃对该对象的锁定之前, 唤醒的线程(awakened thread) 将无法继续.
唤醒的线程 将以普通的方式(usual manner) 与任何其他线程竞争, 这些线程可能正在积极竞争以在此对象上同步. 唤醒的线程在成为下一个 锁定此对象的线程 时没有可靠的特权或缺点(eliable privilege or disadvantage). 换句话说, 被唤醒的线程可能获取到对象的监视器锁, 也可能获取不到.

notify() 方法 只能被 持有对象监视器 的线程调用. 线程通过以下三种方式之一成为对象监视器的所有者:

  1. 通过执行该对象的 synchronized 实例方法.
  2. 执行 synchronized 同步代码块时, 使用该对象做参数.
  3. 对于Class 类型的对象, 执行类的 synchronized 静态方法(For objects of type Class, by executing a synchronized static method of that class.)

在同一时间, 只有一个线程可以持有对象的监视器(锁).

2.2 notifyAll()

public final native void notifyAll();

在功能上和 notify 类似, 不过, notifyAll 会唤醒在此对象监视器上 等待所有 线程.

3 实现生产者-消费者模型

下面实现一个简单的 生产者-消费者 模型.

生产者向队列添加元素:

    public void add(String element) {
        synchronized (queue) {
            while (queue.size() == MAX_SIZE) {
                try {
                    // 挂起当前线程(处于休眠状态)并释放queue上的监视器锁
                    queue.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            // 添加元素
            queue.add(element);
            // 通知唤醒所有消费者线程
            queue.notifyAll();
        }
    }

消费者从队列中获取元素:

    public void take() throws InterruptedException {
        synchronized (queue) {
            while (queue.size() == 0) {
                try {
                    queue.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            // 消费元素
            System.out.println(queue.take());
            // 通知唤醒所有生产者线程
            queue.notifyAll();
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值