wait/notify/interruptのサンプル

追記:「スプリアスウェイクアップ」に対応しました。2008/12/6
http://d.hatena.ne.jp/nowokay/20081206#1228542137


ここまで、いろいろなロックを扱ったのですが、スレッド間での実行の制御も必要になります。
Javaでは、スレッド間の実行の制御にwait/notifyなどが用意されています。
ということで、wait/notifyなどのサンプルを作ってみました


プログラムを開始するとカウントダウンします。


カウントが0になると待機します。

このプログラムではロック用のオブジェクトを用意しています。また再開条件のフラグも用意します。

Object lock = new Object();
boolean flag = false;

待機するときは、オブジェクトのwaitメソッドを呼び出します。このオブジェクトに対してnotifyが呼び出されるまで、ここで待機します。このとき、notifyがなくてもwaitが再開してしまう「スプリアスウェイクアップ」という問題があるので、再開条件のチェックも必要です。

while(!flag){
    lock.wait();
}

waitメソッドは、そのオブジェクトでのsynchronizedブロックで使う必要があります。

synchronized(lock){


「再開」ボタンを押すと「きた」と表示されます。

このとき、lockオブジェクトのnotifyメソッドを呼び出しています。notifyメソッドも、呼び出すときには対象オブジェクトでsynchronizedしておく必要があります。再開条件を満たすようにフラグも設定します。

synchronized (lock){
    flag = true;
    lock.notify();
}


カウントダウン時や待機中に中断ボタンを押すと「中断」となります。

ここでは、スレッドのinterruputメソッドを呼び出します。

t.interrupt();

そうすると、sleepメソッドかwaitメソッドの実行時にInterruputedExceptionが発生します。

catch (InterruptedException ex) {
     tf.setText("中断");
}

このように、interrupt/InterruptedExceptionを使うと、スレッドをきれいに中断することができます。
スレッドを中断する命令としてstopメソッドがありますが、こちらは強制的にスレッドを中断します。finallyブロックは呼び出されますが、処理が中途半端になってしまう可能性があるので、非推奨になっています。


ところで、カウントダウン中に「再開」ボタンを押すとボタン処理が固まって、しばらくして「きた!」になります。

そういえば、waitメソッドもnotifyメソッドも、両方lockオブジェクトでsynchronizedしています。単純に考えると、このままではnotifyメソッドはロックが取得できず、デッドロックになってしまいそうです。
waitメソッドを呼び出すと、そのオブジェクトのロックが一時的に開放されます。そこでnotifyスレッドがロックを取得します。notifyを呼び出すと、そのnotifyスレッドがロックを開放してから、waitスレッドが再びロックを取得して実行が再開します。


ということで、ソース

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class NotifySample {
    public static void main(String[] args){
        JFrame f = new JFrame("Notifyサンプル");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLayout(new FlowLayout());

        final JTextField tf = new JTextField();
        tf.setColumns(12);
        f.add(tf);

        //ロック用オブジェクト
        final Object lock = new Object();
        final boolean[] flag = {false};

        //スレッド
        final Thread t = new Thread(){
            @Override
            public void run() {
                synchronized (lock){
                    try {
                        for(int i = 3; i > 0; --i){
                            tf.setText("カウント" + i);
                            Thread.sleep(1000);
                        }
                        tf.setText("待機中");
                        flag[0] = false;
                        while(!flag[0]){
                            lock.wait();
                        }
                        tf.setText("きた!");
                    } catch (InterruptedException ex) {
                        tf.setText("中断");
                    }
                }
            }

        };
        t.start();

        JButton b;
        //再開ボタン
        b = new JButton("再開");
        f.add(b);
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //ボタン押されたらnotify
                synchronized (lock){
                    flag[0] = true;
                    lock.notify();
                }
            }
        });

        //中断
        b = new JButton("中断");
        f.add(b);
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //ボタン押されたらinterrupt
                t.interrupt();
            }
        });

        f.pack();
        f.setVisible(true);
    }
}