Java线程和并发 - Waiting and Notification

Java线程和并发 - Waiting and Notification

Java提供了线程间通信的API-一个线程可以等一个条件继续执行,在未来,另一个线程会增加该条件,然后提醒等待中的线程。。

Wait-and-Notify API

Object类提供了Wait-and-Notify API,由三个wait()方法、一个notify()方法还有一个notifyAll()方法组成。wait()方法等待一个ie条件,notify()和notifyAll()方法提醒等待中的线程:

  • void wait():导致当前线程等待,直到另一个线程调用了该对象的notify()方法或者notifyAll()方法。当前线程在等待中的时候,可以被其他线程中断
  • void wait(long timeout):当前线程最多等timeout毫秒,如果timeout是负的,抛IllegalArgumentException异常
  • void wait(long timeout, int nanos):当前线程最多等待timeout毫秒和nanos纳秒。如果timeout或者nanos是负的,或者nanos大于999999,抛IllegalArgumentException
  • void notify():唤醒一个等待该对象的monitor的线程。如果有任何线程等待该对象,其中一个被唤醒。选择是任意的,由实现决定。当前线程解锁以后,被唤醒的线程才可以继续执行
  • void notifyAll():唤醒等待该对象的monitor的所有线程

该API利用一个对象的条件队列,等待中的线程也叫wait set。因为条件队列紧紧地绑定到一个对象的锁,所有的五个方法必须在一个同步的环境下被调用(当前线程必须是对象monitor的所有者);否则,抛IllegalMonitorStateException。
下面的伪代码演示了无参数的wait()方法:

    synchronized(obj) {
        while (<condition does not hold>)
            obj.wait();
        // 执行适合条件的操作
    }

这样,在一个synchronized块内调用wait()方法,因为可能伪唤醒(线程醒来,但是没有被notified、中断和超时),所以当条件不满足,就一直等待。循环退出后,开始执行符合条件的动作。
警告:不要在循环体外调用wait()方法。循环在wait()调用之前和之后测试条件。在之前测试确保还是活的。如果不做测试,而且在调用wait()之前调用了notify(),等待的线程可能不会被唤醒。在调用wait()之后做测试可以确保安全。如果不重做测试,而且线程被唤醒后条件不成立(比如其他线程调用了notify()),线程会销毁手锁保护的不变性。

对应的notify()方法可能是这样调用的:

    synchronized(obj) {
        // 设置条件
        obj.notify();
    }

使用相同的对象引用调用notify()方法。
Difference between notify() and notifyAll()
有更多的讨论。如果你想知道该用哪个方法,如果只有两个相关线程,我会使用notify(),其中一个线程偶尔会等待另一个线程的通知。否则,我使用notifyAll()。

生产者和消费者

涉及conditions的线程间通信的经典例子是生产者线程和消费者线程。生产者线程生产由消费者线程消费的数据。每个数据保存在共享变量中。
设想线程运行速度不同的场景。生产的速度可能比消费的速度快,当然也有可能,消费者可能要等待数据的生成。要解决这些问题,生产者线程必须等待,直到它被提醒先前生产的数据被消费掉了,消费者也得等待新的数据被生产出来:

public class PC {
    public static void main(String[] args) {
        Shared s = new Shared();
        new Producer(s).start();
        new Consumer(s).start();
    }

    private static class Shared {
        private char c;
        private volatile boolean writeable = true;

        synchronized void setSharedChar(char c) {
            while (!writeable)
                try {
                    wait();
                } catch (InterruptedException ie) {
                }
            this.c = c;
            writeable = false;
            notify();
        }

        synchronized char getSharedChar() {
            while (writeable)
                try {
                    wait();
                } catch (InterruptedException ie) {
                }
            writeable = true;
            notify();
            return c;
        }
    }

    private static class Producer extends Thread {
        private final Shared s;

        Producer(Shared s) {
            this.s = s;
        }

        @Override
        public void run() {
            for (char ch = 'A'; ch <= 'Z'; ch++) {
                s.setSharedChar(ch);
                System.out.println(ch + " produced by producer.");
            }
        }
    }

    private static class Consumer extends Thread {
        private final Shared s;

        Consumer(Shared s) {
            this.s = s;
        }

        @Override
        public void run() {
            char ch;
            do {
                ch = s.getSharedChar();
                System.out.println(ch + " consumed by consumer.");
            } while (ch != 'Z');
        }
    }
}

上面的程序增加了一个Shared对象和两个拥有该对象的引用的线程。生产者调用setSharedChar()方法保存23个大写字母,消费者调用getSharedChar()方法获取每个字母。
writeable实例成员跟踪两个条件:生产者等待消费数据和消费者等待生产数据。它负责协调生产者和消费者。程序是这样执行的:

  • 消费者执行s.getSharedChar()来检索字母
  • 在同步方法内,因为writeable是true,消费者调用wait()。消费者等待数据生成的提醒
  • 生产者终于执行了setSharedChar方法
  • 当生产者进入同步方法的时候(这是有可能的,因为消费者在wait()方法内释放锁),发现writeable是true,不能调用wait()
  • 生产者保存字母,设置writeable为false(这会导致在消费者还没消费该字母的时候,生产者等待下一次setSharedChar方法调用),并且调用notify()提醒消费者(假设消费者在等待)
  • 生产者退出setSharedChar方法
  • 消费者醒来,设置writeable为true(这会导致生产者没有生产出字母前,消费者等待下一次getSharedChar调用),提醒生产者唤醒消费者线程,返回共享的字母

你能获得类似这样的输出:

W produced by producer.
W consumed by consumer.
X produced by producer.
X consumed by consumer.
Y produced by producer.
Y consumed by consumer.
Z produced by producer.
Z consumed by consumer.

虽然同步工作正常,但是你可能在多个消费消息前,观察到多个生产消息:

A produced by producer.
B produced by producer.
A consumed by consumer.
B consumed by consumer.

甚至可能看到消费消息在生产消息之前:

Q consumed by consumer.
Q produced by producer.

奇怪的输出顺序,不是说生产者和消费者线程没有同步。而是setSharedChar()调用后面的System.out.println()没有同步,getSharedChar调用后面的println也没有同步。所以,可以这样修改程序:

public class PC {
    public static void main(String[] args) {
        Shared s = new Shared();
        new Producer(s).start();
        new Consumer(s).start();
    }

    private static class Shared {
        private char c;
        private volatile boolean writeable = true;

        synchronized void setSharedChar(char c) {
            while (!writeable)
                try {
                    wait();
                } catch (InterruptedException ie) {
                }
            this.c = c;
            writeable = false;
            notify();
        }

        synchronized char getSharedChar() {
            while (writeable)
                try {
                    wait();
                } catch (InterruptedException ie) {
                }
            writeable = true;
            notify();
            return c;
        }
    }

    private static class Producer extends Thread {
        private final Shared s;

        Producer(Shared s) {
            this.s = s;
        }

        @Override
        public void run() {
            for (char ch = 'A'; ch <= 'Z'; ch++) {
                synchronized (s) {
                    s.setSharedChar(ch);
                    System.out.println(ch + " produced by producer.");
                }
            }
        }
    }

    private static class Consumer extends Thread {
        private final Shared s;

        Consumer(Shared s) {
            this.s = s;
        }

        @Override
        public void run() {
            char ch;
            do {
                synchronized (s) {
                    ch = s.getSharedChar();
                    System.out.println(ch + " consumed by consumer.");
                }
            } while (ch != 'Z');
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值