技術メモ

技術メモ

ラフなメモ

Javaのwait,notify,notifyAll,yieldを試す

スレッド間で通信するための仕組みに wait(), notifiAll(), notify() があります。またスレッドのCPUリソースの委譲に yield() があります。各メソッドを試してみようと思います。

wait()

https://docs.oracle.com/javase/jp/8/docs/api/java/lang/Object.html#wait--

以下の事象が発生するまでスレッドは待機し続けます。

  • notify() がオブジェクトに対して呼ばれて、そのスレッドが実行可能と選択された場合
  • オブジェクトに対して nofityAll() が呼ばれた場合
  • wait(long timeout) において timeout 時間経過後
  • スレッドに対して interrupt() が呼ばれた場合
public class Sample {

    public static void main(String[] args) {

        WaitOnly waitOnly = new WaitOnly();
        Runnable task = waitOnly::threadWait;
        new Thread(task).start();

    }
}

class WaitOnly {
    synchronized void threadWait() {
        try {
            System.out.println("wait!");
            wait();
            System.out.println("unlocked!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

このスレッドは待機し続けます。

結果

[Thread-0] :wait!
(...)

notify()

https://docs.oracle.com/javase/jp/8/docs/api/java/lang/Object.html#notify--

このオブジェクトのモニターで待機中のスレッドを1つ再開します。このオブジェクトで複数のスレッドが待機中の場合は、そのうちの1つを再開します。この選択は任意で、実装によって異なります。

public class Sample {

    public static void main(String[] args) throws InterruptedException {

        WaitAndNotify waitAndNotify = new WaitAndNotify();
        Runnable taskWait = waitAndNotify::threadWait;
        new Thread(taskWait).start();

        Thread.sleep(5000);

        Runnable taskNotify = waitAndNotify::threadNotify;
        new Thread(taskNotify).start();

    }
}

class WaitAndNotify {

    synchronized void threadWait() {
        System.out.printf("[%s] :%s\n", Thread.currentThread().getName(), "wait!");
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("[%s] :%s\n", Thread.currentThread().getName(), "unlocked!");
    }

    synchronized void threadNotify() {
        notify();
        System.out.printf("[%s] :%s\n", Thread.currentThread().getName(), "notified!");
    }
}

結果
別スレッドからwait() していたスレッドに notify() されて、ロックが開放されていることが分かります。

[Thread-0] :wait!
[Thread-1] :notified!
[Thread-0] :unlocked!

notifyAll()

public class Sample {

    public static void main(String[] args) throws InterruptedException {

        WaitAndNotify waitAndNotify = new WaitAndNotify();
        Runnable task = waitAndNotify::threadWait;
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();

        Thread.sleep(5000);

        Runnable taskNotify = waitAndNotify::threadNotifyAll;
        new Thread(taskNotify).start();

    }
}

class WaitAndNotify {

    synchronized void threadWait() {
        System.out.printf("[%s] :%s\n", Thread.currentThread().getName(), "wait!");
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("[%s] :%s\n", Thread.currentThread().getName(), "unlocked!");
    }

    synchronized void threadNotify() {
        notify();
        System.out.printf("[%s] :%s\n", Thread.currentThread().getName(), "notified!");
    }

    synchronized void threadNotifyAll() {
        notifyAll();
        System.out.printf("[%s] :%s\n", Thread.currentThread().getName(), "notified all!");
    }
}

結果
notifyAll() が別スレッドから呼ばれて、waitしていたスレッドのロックが開放されていることが分かります。

[Thread-0] :wait!
[Thread-4] :wait!
[Thread-3] :wait!
[Thread-2] :wait!
[Thread-1] :wait!
[Thread-5] :notified all!
[Thread-1] :unlocked!
[Thread-2] :unlocked!
[Thread-3] :unlocked!
[Thread-4] :unlocked!
[Thread-0] :unlocked!

yield()

https://docs.oracle.com/javase/jp/7/api/java/lang/Thread.html#yield()

スケジューラにカレントスレッドを実行させる必要がないというヒントを提供します。厳密な保証はありません。

譲位は、通常であれば CPU を過剰に使用してしまうスレッド間で相対的な進行状況を改善しようとするヒューリスティックな試みです。

public class Sample {

    public static void main(String[] args) {

        Runnable taskA = new MyYeild(true, "ping")::foo;
        Runnable taskB = new MyYeild(false, "PONG")::foo;

        new Thread(taskA).start();
        new Thread(taskB).start();

    }

}

class MyYeild {

    final boolean isYield;
    final String word;

    public MyYeild(boolean isYield, String word) {
        this.isYield = isYield;
        this.word = word;
    }

    void foo() {
        for (int i = 0; i < 10; i++) {
            if (isYield) {
                System.out.printf("[%s] :%s\n", Thread.currentThread().getName(), "yield!");
                Thread.yield();
            }
            System.out.printf("[%s] :%s\n", Thread.currentThread().getName(), word);
        }
    }

}

結果
結果は実行時によりけりです。isYieldがともにfalseの場合は、割り込みがほぼ発生しないように見えますが、trueにすると以下のように割り込みが入る場合が増えます。

[Thread-0] :yield!
[Thread-1] :PONG
[Thread-1] :PONG
[Thread-1] :PONG
[Thread-1] :PONG
[Thread-1] :PONG
[Thread-0] :ping
[Thread-0] :yield!
[Thread-0] :ping
[Thread-0] :yield!
[Thread-0] :ping
[Thread-0] :yield!
[Thread-0] :ping
[Thread-0] :yield!
[Thread-0] :ping

参考

プログラミング言語 Java 第4版

プログラミング言語 Java 第4版