Javaでマルチスレッドプログラミング -スレッドの同期-

Javaでマルチスレッドプログラミングを覚えようと、こちらのサイトを読んで写経してみた。前回の記事に引き続き、今回はスレッドの同期について。
 

他のスレッドの終了を待つ

join()

別のスレッドにある処理を任せ、そのスレッドが終了したときに、自分のスレッドの処理を再開したいという場面がある。このようなとき、Threadクラスのjoinメソッドを利用する。
 

void join();
void join(long millis)
void join(long millis, int nanos)

 
join()は該当のスレッドが終了するのを待ちつづける。引数のあるものは、該当のスレッドが終了しなくても、指定した時間経過すれば処理を再開する。いずれのメソッドも、外部のスレッドよりinterruptメソッドで割り込まれた場合、InterruptedExceptionを発生して処理を終了する。
 

public class JoinTest extends Thread {
    public void run() {
        for (int i = 3; i >= 0 ; i--) {
            try {
                sleep(1000);
            } catch (InterruptedException e) {}
            System.out.println(i);
        }
    }
 
    public static void main(String[] args) {
        JoinTest t = new JoinTest();
        t.start();
        try {
            System.out.println("スレッド t の終了を待機します。");
            t.join(); // カウントダウンが終了するのを待つ
            System.out.println("スレッド t が終了しました。");
        } catch (InterruptedException e) {}
    }
}

 
この例では以下のような出力が行われる。

スレッド t の終了を待機します。
3
2
1
0
スレッド t が終了しました。

 

スレッド間の待ち合わせ

複数のスレッドが協調して処理を行う場合、あるスレッドの部分処理が終了するのを、もう一方のスレッドが待機したい場合がある。そのようなとき、java.lang.Objectクラスのwait、notify、notifyAllメソッドを使用する。
 

wait()

waitメソッドは、notifyまたはnotifyAllメソッドが呼び出されるまで処理を待機するメソッドである。waitメソッドを呼び出すためには、スレッドは該当するオブジェクトのロックを取得していなければならない。
 

notify()、notifyAll()

notifyおよびnotifyAllメソッドは、waitメソッドによって待機状態にあるスレッドの実行を再開させるメソッドである。あるオブジェクトに関して、waitメソッドで待機しているスレッドの集合のことを「ウェイトセット」と呼ぶ。
 
notifyメソッドは、ウェイトセット内の1つのスレッドの処理を再開させる。再開されるスレッドは、Java仮想マシンによって任意に選ばれ、プログラムで指定することはできない。
 
notifyAllメソッドは、ウェイトセット内の全てのスレッドの処理を再開させる。notifyおよびnotifyAllメソッドを実行するためには、waitメソッドの場合と同じく、該当オブジェクトのロックを取得していなければならない。
 

例として、wait()及びnotifyAll()を使ってスレッド間の待ち合わせを行う処理を考えてみる。
 

今回登場するクラス
  • QueueTestクラス – mainメソッドが記述されたクラス。Producer、Consumerクラスのインスタンスを作成する。
  • Queueクラス – キューを管理するクラス。ゲッター(getter)とセッター(setter)を持つ。
  • Producerクラス – 数字を生産(Produce)し、キューへと格納するクラス。Queueクラスのセッターを呼び出す。
  • Consumer – 数字を消費(Consume)するためにキューから取り出すクラス。Queueクラスのゲッターを呼び出す。

 

プログラムの概要

Queueクラスで管理されているキュー(queue変数)には、最大で3つまでの数字を格納することが出来る。
Producerクラスがキューへ数字を格納し、Consumerクラスがキューから数字を取り出す。
もし、キューの中に3つ数字が格納されていて、これ以上格納できないのであれば、Producerクラスはwait()を呼び出す。
もし、キューの中に1つも数字が格納されていないのであれば、Consumerクラスはwait()を呼び出す。
Producerクラスがwait()した場合、Consumerクラスによってキューに格納された数字が2個以下になるまで取り出され、notifyAll()が呼び出されるまで待機する。
Consumerクラスがwait()した場合、Producerクラスによってキューに数字が1つ以上格納され、notifyAll()が呼び出されるまで待機する。
 

import java.util.LinkedList;
 import java.util.Iterator;
  
 public class QueueTest {
   public static void main(String[] args) {
     Queue queue = new Queue();
     new Producer(queue).start();
     new Consumer(queue).start();
   }
}
 
class Queue {
  LinkedList queue;
  public Queue() {
    queue = new LinkedList();
  }
  synchronized public void put(Object obj) {
    while (queue.size() >= 3) {
      System.out.println(obj + "を追加しようとしましたが,"
                         + "キューがいっぱいなので待機します。");
      try {
        wait();
      } catch (InterruptedException e) {}
    }
    queue.addFirst(obj);
    printList();
    System.out.println(obj + "をキューに追加しました。");
    notifyAll();
  }
  synchronized public Object get() {
    while (queue.size() == 0) {
      System.out.println("データを取り出そうとしましたが,"
                         + "キューが空なので待機します。");
      try {
        wait();
      } catch (InterruptedException e) {}
    }
    Object obj = queue.removeLast();
    printList();
    System.out.println(obj + "をキューから取り出しました。");
    notifyAll();
    return obj;
  }
  synchronized public void printList() {
    System.out.print("[ ");
    for (Iterator i = queue.iterator(); i.hasNext();) {
      System.out.print(i.next() + " ");
    }
    System.out.print("]  ");
  }
}
 
class Producer extends Thread {
  private Queue queue;
  public Producer(Queue queue) {
    this.queue = queue;
  }
  public void run() {
    for (int i = 0; i < 100; i++) {
      try {
        Thread.sleep((long)(Math.random()*1000));
      } catch (InterruptedException e) {}
      queue.put(new Integer(i));
    }
  }
}
 
class Consumer extends Thread {
  private Queue queue;
  public Consumer(Queue queue) {
    this.queue = queue;
  }
  public void run() {
    for (int i = 0; i < 100; i++) {
      try {
        Thread.sleep((long)(Math.random()*1000));
      } catch (InterruptedException e) {}
      Object obj = queue.get();
    }
  }
}

 

実行結果
[ 0 ] 0をキューに追加しました。
[ ] 0をキューから取り出しました。
データを取り出そうとしましたが,キューが空なので待機します。
[ 1 ] 1をキューに追加しました。 
[ ] 1をキューから取り出しました。
[ 2 ] 2をキューに追加しました。
[ ] 2をキューから取り出しました。
[ 3 ] 3をキューに追加しました。
[ 4 3 ] 4をキューに追加しました。
[ 5 4 3 ] 5をキューに追加しました。
6を追加しようとしましたが,キューがいっぱいなので待機します。
[ 5 4 ] 3をキューから取り出しました。
[ 6 5 4 ] 6をキューに追加しました。

 
 
以上
 
 
参考
マルチスレッドプログラミング | TECHSCORE(テックスコア)

Article written by

Comments are closed, but trackbacks and pingbacks are open.