スレッドの排他制御と実行順序

引き続き「増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編」 を読んでマルチスレッドプログラミングについて勉強している.現在は第一章『Single Thread Execution』部分を読んでいるところである.この章ではマルチスレッドプログラミングで最も基本となる,”1つのスレッドによる実行”をテーマとしている.ここでは一度に実行するスレッドは一つと限定されており,synchronized を使って排他制御を実現している.

ここでは,synchronized を用いた場合とそうでない場合の例題プログラムの比較を行い,排他制御の重要性を論じている.ここで紹介されている例題は,synchronized を使用することで排他制御を実現し,各スレッドでのデータ整合性を崩さずに繰り返しデータ更新を行うというものなのだが一つわからないことがある.

例題で作成した main メソッドは以下のようなものである.本来 Gate クラスと UserThread クラスがあるがここでは省略する.

/*
 TestSingleThreadedExecution.java
 */

public class TestSingleThreadedExecution {
    public static void main (String[] args) {
	System.out.println( "Testing Gate, hit CTRL+C to exit." );
	Gate gate = new Gate();

	Thread aliceThread = new UserThread ( gate, "Alice", "Alaska" );
	aliceThread.start();

	Thread bobbyThread = new UserThread ( gate, "Bobby", "Brazil" );
	bobbyThread.start();

	Thread chrisThread = new UserThread ( gate, "Chris", "Canada" );
	chrisThread.start();
    }
}

これを実行するとスレッドが生成され,

Alice BEGIN
Bobby BEGIN
Chris BEGIN

と標準出力される,その後,各スレッドが繰り返し実行され,データの不整合が起こった場合にそれを出力するようなプログラムである.これを何度も実行していると,時々以下のように Chris と Bobby が作成する順番が逆になることがあった.

Alice BEGIN
Chris BEGIN
Bobby BEGIN

どうやらプログラムに書かれている順番とスレッド作成の順番は必ずしも一致するわけではないようである.これは当然 synchronized を付加して排他制御を実現した場合でも起こる.synchronized はメソッド実行をロックにより制御しているだけであり,順番までは制御することができない.

プログラムに書いたとおりにスレッドが作成されないとなると,なおのことマルチスレッドプログラミングは難しいなあという感じが強まった.まだスレッド1つずつしか実行していないのだけれど,複数スレッドを同時に実行するとなおのこと管理が難しいだろう.この順番の話も読み進めていくうちに解決されるのだろうか.

「増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編」 Introduction 部分読了

「増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編」の Introduction 部分を読了.この部分ではスレッドの作成・開始の仕方や, synchronized による排他制御など,基本的な事項の説明がなされていた.

まだ内容自体が簡単でありある程度知識もあるのでサクサク読めたが,「継承と実装でのスレッド作成方法の違い」と「synchronized による排他制御の範囲」は気になったのでメモしておく.

  • 「継承と実装でのスレッド作成方法の違い」

単純に,クラス名のあとに extends Thread と書くか implements Runnable とつけるだけとおもいきや,実は作成ステップが少し異なっているようだ.

継承では基本的に,

  1. Thread クラスを継承したクラスのインスタンスを作成する.
  2. 作成したインスタンスの start() メソッドを呼び出す.

という 2 ステップでスレッド作成・開始を行うようである.

一方実装では,

  1. Runnable インターフェイスを実装したクラスのインスタンスを作成する.
  2. Thread クラスのインスタンスを作成し,引数として 1 で作成したインスタンスを使用する.
  3. Thread インスタンスの start() メソッドを呼び出す.

という 3 ステップとなっている.

どちらもごっちゃになっていた.というか僕の継承と実装の理解が足りてないだけの気もする.

演習問題より.普通のインスタンスメソッドはいいとして,static 付きのクラスメソッドについて.

例えば以下のような Something クラスを用意する,.

public class Something {
    public static void CA () {}
    public static void CB () {}
    public static synchronized void syncCA () {}
    public static synchronized void syncCB () {}
}

この Something クラスからx と y というインスタンスを作成したとする.その時,複数のスレッドで同時に実行できる組はどれかという問題である.

synchronized が付いていない者同士は static の有無に関係なく同時実行可能である.synchronized が付いているものと付いていないものも同じく.しかし,"Something.syncCA" と "Something.syncCB" は同時実行できない.また,別々のインスタンスから呼び出した "x.syncCA" と "y.syncCB" も同時実行できない.というかこれに関しては呼び出し方としてそもそもあまりよくない.

また,synchronized 付きのクラスメソッドとインスタンスメソッドでは,ロックが掛かる対象が異なるので同時実行が可能なようだ.

序盤からいきなりマルチスレッドプログラミングの難しさを垣間見ることができた.

ブログへのソースコード貼り付けテスト

今後プログラミングの勉強成果を残していくにあたり,ソースコードの貼り付けは必須だと思う.見やすくするために,シンタックス・ハイライトにより色分けを行う.

ハイライト方法は,はてなダイアリーヘルプの「ソースコードを色付けして記述する(シンタックス・ハイライト)」にて紹介されている.

基本的にはソースコードの文頭と文末にそれぞれ ">|(ソースコードの言語名)|" と "||<" をつけるだけでいいようだ.以下は本日練習問題で作成した 3 つのソースコードへのシンタックス・ハイライト適用例である.

1つ目:

// TestSimpleThread.java

public class TestSimpleThread {
    public static void main (String[] args) {
	Bank bank = new Bank ("Ginko", 1000);
	MyThread thread1 = new MyThread ( bank );
	MyThread thread2 = new MyThread ( bank );

	thread1.start();
	thread2.start();
    }
}


2つ目:

// MyThread.java

public class MyThread extends Thread {
    private Bank bank;
    
    public MyThread( Bank bank ){
	this.bank = bank;
    }
    
    public void run() {
	while (true) {
	    boolean ok = bank.withdraw( 1000 );
	    if ( ok )
		bank.deposit( 1000 );
	}
    }
}

3つ目

// Bank.java

public class Bank {
    private int money;
    private String name;

    public Bank (String name, int money) {
	this.name = name;
	this.money = money;
    }

    public synchronized void deposit (int m) {
	money += m;
    }

    public synchronized boolean withdraw (int m) {
	if ( money >= m ) {
	    money -= m;
	    check();
	    return true;
	} else {
	    return false;
	}
    }

    public String getName() {
	return name;
    }

    private void check () {
	if ( money < 0 )
	    System.out.println( "預金残高がマイナスです! money = " + money );
    }
}

Java によるマルチスレッドを勉強し始めた

Java の基礎をある程度身につけたので,次の学習内容としてマルチスレッドを選ぶことにした.マルチスレッドを学ぶにあたり,以下の本を購入した.

この本はイントロダクションと本題の二部構成となっている.イントロダクションでは,スレッドの作成・開始方法や "syncronized" によるスレッドの排他制御など,基本的な事項が簡潔に説明されている.その後に,本題でより詳しくスレッドによる Java プログラミングの解説がなされているようだ.

イントロダクションを読み終わった.非常に丁寧で僕程度の Java 知識でもサクサク読み進めることが出来る.練習問題がたくさん収録されており,非常に丁寧な解説も併録されているのも好印象だ.

しばらくはこの本を参考に Java のマルチスレッドプログラミングを勉強していこうと思う.