• Java 中 wait notify notifyAll 实现线程通讯

  • Tue Dec 11 22:56:07 CST 2012 编程者 我要收藏(...) 评论 ...
  • 当线程在继续执行前需要等待一个条件方可继续执行时,仅有 synchronized 关键字是不够的。因为虽然synchronized关键字可以阻止并发更新同一个共享资源,实现了同步,但是它不能用来实现线程间的消息传递,也就是所谓的通信。
  • 在现实应用中,很多时候都需要让多个线程按照一定的次序来访问共享资源,例如,经典的生产者和消费者问题。这类问题描述了这样一种情况,假设仓库中 只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。如果仓库中没有产品,则生产者可以将产品放入仓库,否则停止生产并等 待,直到仓库中的产品被消费者取走为止。如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。显然,这是 一个同步问题,生产者和消费者共享同一资源,并且,生产者和消费者之间彼此依赖,互为条件向前推进。但是,该如何编写程序来解决这个问题呢?

    传统的思路是利用循环检测的方式来实现,这种方式通过重复检查某一个特定条件是否成立来决定线程的推进顺序。比如,一旦生产者生产结束,它就继续利 用循环检测来判断仓库中的产品是否被消费者消费,而消费者也是在消费结束后就会立即使用循环检测的方式来判断仓库中是否又放进产品。显然,这些操作是很耗 费CPU资源的,不值得提倡。那么有没有更好的方法来解决这类问题呢?

    首先,当线程在继续执行前需要等待一个条件方可继续执行时,仅有 synchronized 关键字是不够的。因为虽然synchronized关键字可以阻止并发更新同一个共享资源,实现了同步,但是它不能用来实现线程间的消息传递,也就是所谓 的通信。而在处理此类问题的时候又必须遵循一种原则,即:对于生产者,在生产者没有生产之前,要通知消费者等待;在生产者生产之后,马上又通知消费者消 费;对于消费者,在消费者消费之后,要通知生产者已经消费结束,需要继续生产新的产品以供消费。

    其实,Java提供了3个非常重要的方法来巧妙地解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。它们都是Object类的最终方法,因此每一个类都默认拥有它们。

    虽然所有的类都默认拥有这3个方法,但是只有在synchronized关键字作用的范围内,并且是同一个同步问题中搭配使用这3个方法时才有实际的意义。

    这些方法在Object类中声明的语法格式如下所示:

    final void wait() throws InterruptedException

    final void notify()

    final void notifyAll()

    其中,调用wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行态退出,进入等待队列,直到被再次唤醒。而调用notify()方法可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行态。调用notifyAll()方法可以使所有正在等待队列中等待同一共享资源的线程从等待状态退出,进入可运行状态,此时,优先级最高的那个线程最先执行。显然,利用这些方法就不必再循环检测共享资源的状态,而是在需要的时候直接唤醒等待队列中的线程就可以了。这样不但节省了宝贵的CPU资源,也提高了程序的效率。

    由于wait()方法在声明的时候被声明为抛出InterruptedException异常,因此,在调用wait()方法时,需要将它放入try…catch代码块中。此外,使用该方法时还需要把它放到一个同步代码段中,否则会出现如下异常:

    java.lang.IllegalMonitorStateException: current thread not owner

    这些方法是不是就可以实现线程间的通信了呢?下面将通过多线程同步的模型: 生产者和消费者问题来说明怎样通过程序解决多线程间的通信问题。

    下面这个程序演示了多个线程之间进行通信的具体实现过程。程序中用到了4个类,其中ShareData类用来定义共享数据和同步方法。在同步方法中调用了wait()方法和notify()方法,并通过一个信号量来实现线程间的消息传递。

    通过程序的运行结果可以看到,尽管在主方法中先启动了Consumer线程,但是,由于仓库中没有产品,因此,Consumer线程就会调用 wait()方法进入等待队列进行等待,直到Producer线程将产品生产出来并放进仓库,然后使用notify()方法将其唤醒。

    class ShareData  {
        private char c;
        private boolean isProduced = false; // 信号量
        public synchronized void putShareChar(char c) // 同步方法putShareChar()
        {
            if (isProduced) // 如果产品还未消费,则生产者等待
            {
                try
                {
                    wait(); // 生产者等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.c = c;
            isProduced = true; // 标记已经生产
            notify(); // 通知消费者已经生产,可以消费
        }

        public synchronized char getShareChar() // 同步方法getShareChar()
        {
            if (!isProduced) // 如果产品还未生产,则消费者等待
            {
                try
                {
                    wait(); // 消费者等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            isProduced = false; // 标记已经消费
            notify(); // 通知需要生产
            return this.c;
        }
    }

    class Producer extends Thread // 生产者线程 {
        private ShareData s;
      
        Producer(ShareData s)
        {
            this.s = s;
        }

        public void run()
        {
            for (char ch = 'A'; ch <= 'D'; ch++)
            {
                try
                {
                    Thread.sleep((int) (Math.random() * 3000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                s.putShareChar(ch); // 将产品放入仓库
                System.out.println(ch + " is produced by Producer.");
            }
        }
    }

    class Consumer extends Thread // 消费者线程 {
        private ShareData s;

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

        public void run()
        {
            char ch;
            do {
                try
                {
                    Thread.sleep((int) (Math.random() * 3000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ch = s.getShareChar(); // 从仓库中取出产品
                System.out.println(ch + " is consumed by Consumer. ");
            } while (ch != 'D');
        }
    }

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

    由于在两个线程中都指定了一定的休眠时间,因此也可能出现这样的情况:生产者将产品生产出来放入仓库,并通知等待队列中的Consumer线程,然而,由于休眠时间过长,Consumer线程还没有打算消费产品,此时,Producer线程欲生产下一个产品,结果由于仓库中的产品没有被消费掉,故 Producer线程执行wait()方法进入等待队列等待,直到Consumer线程将仓库中的产品消费掉以后通过notify()方法去唤醒等待队列中的Producer线程为止。可见,两个线程之间除了必须保持同步之外,还要通过相互通信才能继续向前推进。

  • 信息来源:http://bianchengzhe.com (举报这篇文章)
  • 好评(...) 中评(...) 差评(...)