■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 2009年06月07日 Java総合講座 - 初心者から達人へのパスポート vol.156 セルゲイ・ランダウ バックナンバー: http://www.flsi.co.jp/Java_text/ ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ [このメールマガジンは、画面を最大化して見てください。] ======================================================== ◆ 01.SOAPのアプリケーション(Webサービス) ======================================================== Axisはサーブレット(Tomcatのアプリケーション)として稼動します(Tomcat以外 のWebコンテナーを使ってもよいが、とにかくAxisはサーブレットとして稼動する) から、複数のクライアントから同時にアクセスが来ると、それぞれ複数のスレッド としてAxisのアプリケーションが実行されることになります。 もう少し具体的に言うと、同時に複数のクライアントからHotelSoapBindingImpl の呼び出しがあった場合、それぞれに対応した複数のAxisのスレッドが作られて、 それぞれのスレッドごとに別々のHotelSoapBindingImplのインスタンスが 生成され、それらのメソッドが呼び出されることになります。 そうすると、HotelSoapBindingImplのfindRooms()やreserveRoom()などの メソッドは別々のスレッドによって同時に複数実行される可能性があります。 そうすると、あるスレッド(Aとする)でfindRooms()メソッドの中の DbManager.connect(); の行を実行した直後に別のスレッドでfindRooms()メソッドまたはreserveRoom() メソッドの DbManager.disconnect(); の行を実行してしまう可能性もあります。 すると、スレッドAでfindRooms()メソッドの続きのデータベース・アクセスの部分 roomInfoVector = RoomInfoDbManager.getData(numOfLodgers, stayInfo.getMinRatePerNight(), stayInfo.getMaxRatePerNight()); を実行しようとしたときに、接続がcloseされているというエラーになって しまいます。 (このように、複数のスレッドが同時に同じ資源を操作しようとする状態を競合状態 (race conditions)と言います。) たしかにDBManagerの全staticメソッド(connect()メソッドとdisconnect()メソッド)を synchronized指定にしておけばconnフィールドには同時に一つのスレッドからしか アクセスできなくなりますが、しかしそれでも、やはりHotelSoapBindingImplのメソッド が複数のスレッドから同時に実行できるかぎり、上の競合状態の問題は解決しません。 それなら、HotelSoapBindingImplのメソッドにもsynchronizedを指定すればいいじゃ ないか、と思うかもしれませんが、それでも問題は解決しません。 インスタンス・メソッドの場合、synchronizedを指定しても別々のインスタンスで あれば同時にメソッドを実行できてしまいますから、やはり競合状態の問題が発生 してしまうのです。 ではHotelSoapBindingImplのメソッドをstaticメソッドにすればいいかというと、 HotelSoapBindingImplのメソッドはインターフェースを実装する形になっている ためstaticメソッドにはできないのです。(インターフェースのメソッドにはstatic は指定できない。) そもそもこれらのメソッドがstaticメソッドでかつsynchronized指定になっていたら 同時に一つだけしか実行できないことになり、マルチ・スレッドになっている意味が ありません。(複数のクライアントからの要求を同時に処理することができません。) 実は、この問題の有効な対処方法は、synchronizedを指定することではなく、 DbManagerのconnフィールドのstatic指定を取り除き、DbManagerおよび そのサブクラスのメソッドからもstatic指定を取り除いてクラス・メソッドでは なくインスタンス・メソッドとして実行することです。 これらのインスタンスはHotelSoapBindingImplのメソッドの中で生成します。 そうすれば、スレッドごとに異なるインスタンスが生成されるため、上の競合状態 の問題は発生しません。 (この対処方法によるソース・コードの修正作業は、次回までの演習としておき、 修正したソース・コード例は次回提示いたします。) そもそも、MySQLを含めた一般のRDBMSでは同時に複数の接続を使用することが できます。connフィールドをstatic指定にしてしまうと、この機能を有効に利用する ことができないのです。 (次回に続く) では、今日はここまでにします。 ====================================================== ◆ 02.演習問題 ====================================================== synchronized指定したメソッドについての前回の説明内容を理解している かどうかを確認するために、以下のようなクラスを作成してみてください。 なお、複数のスレッドで同時に実行しても問題ないメソッドをスレッド・セーフ (thread-safe)と形容します。 -------------------------------------------------------- public class Calc1 { private static int sum = 0; public static int add(int val) { int temp = sum; System.out.println("現在: sum =" + sum); System.out.println("加える数: val =" + val); sum += val; System.out.println("加算の結果: sum =" + sum); System.out.println("===== " + temp + " + " + val + " = " + sum + " ====="); return sum; } } -------------------------------------------------------- -------------------------------------------------------- public class Calc2 { private int sum = 0; public int add(int val) { int temp = sum; System.out.println("現在: sum =" + sum); System.out.println("加える数: val =" + val); sum += val; System.out.println("加算の結果: sum =" + sum); System.out.println("===== " + temp + " + " + val + " = " + sum + " ====="); return sum; } } -------------------------------------------------------- -------------------------------------------------------- public class SynchronizedCalc1 { private static int sum = 0; public synchronized static int add(int val) { int temp = sum; System.out.println("現在: sum =" + sum); System.out.println("加える数: val =" + val); sum += val; System.out.println("加算の結果: sum =" + sum); System.out.println("===== " + temp + " + " + val + " = " + sum + " ====="); return sum; } } -------------------------------------------------------- -------------------------------------------------------- public class SynchronizedCalc2 { private int sum = 0; public synchronized int add(int val) { int temp = sum; System.out.println("現在: sum =" + sum); System.out.println("加える数: val =" + val); sum += val; System.out.println("加算の結果: sum =" + sum); System.out.println("===== " + temp + " + " + val + " = " + sum + " ====="); return sum; } } -------------------------------------------------------- -------------------------------------------------------- public class SynchronizedCalc3 { private static int sum = 0; public synchronized int add(int val) { int temp = sum; System.out.println("現在: sum =" + sum); System.out.println("加える数: val =" + val); sum += val; System.out.println("加算の結果: sum =" + sum); System.out.println("===== " + temp + " + " + val + " = " + sum + " ====="); return sum; } } -------------------------------------------------------- -------------------------------------------------------- // 普通のstaticメソッドの実行(スレッド・セーフでない) public class SynchronizedTest1 implements Runnable { public void run() { for (int i = 0; i < 10; i++) { Calc1.add(i); } } public static void main(String[] args) { SynchronizedTest1 sTest = new SynchronizedTest1(); Thread[] aThread = new Thread[10]; for (int i = 0; i < 10; i++) { aThread[i] = new Thread(sTest); aThread[i].start(); } } } -------------------------------------------------------- -------------------------------------------------------- // 普通のインスタンス・メソッドの実行(スレッド・セーフでない) // (1つのインスタンスを複数のスレッドで同時に実行) public class SynchronizedTest2 implements Runnable { private Calc2 calc2 = new Calc2(); public void run() { for (int i = 0; i < 10; i++) { calc2.add(i); } } public static void main(String[] args) { SynchronizedTest2 sTest = new SynchronizedTest2(); Thread[] aThread = new Thread[10]; for (int i = 0; i < 10; i++) { aThread[i] = new Thread(sTest); aThread[i].start(); } } } -------------------------------------------------------- -------------------------------------------------------- // synchronizedのstaticメソッドの実行(スレッド・セーフ) public class SynchronizedTest3 implements Runnable { public void run() { for (int i = 0; i < 10; i++) { SynchronizedCalc1.add(i); } } public static void main(String[] args) { SynchronizedTest3 sTest = new SynchronizedTest3(); Thread[] aThread = new Thread[10]; for (int i = 0; i < 10; i++) { aThread[i] = new Thread(sTest); aThread[i].start(); } } } -------------------------------------------------------- -------------------------------------------------------- // synchronizedのインスタンス・メソッドの実行(スレッド・セーフ) // (1つのインスタンスを複数のスレッドで同時に実行) public class SynchronizedTest4 implements Runnable { private SynchronizedCalc2 calc2 = new SynchronizedCalc2(); public void run() { for (int i = 0; i < 10; i++) { calc2.add(i); } } public static void main(String[] args) { SynchronizedTest4 sTest = new SynchronizedTest4(); Thread[] aThread = new Thread[10]; for (int i = 0; i < 10; i++) { aThread[i] = new Thread(sTest); aThread[i].start(); } } } -------------------------------------------------------- -------------------------------------------------------- // 普通のインスタンス・メソッドの実行 // (複数のスレッドで別々にインスタンスを生成して同時に実行) // (Calc2のメソッドはスレッド・セーフとは言えないが、このよう) // (な呼び出し方であれば、競合条件は発生しない。) // (ただし、標準出力への書き出しに関しては競合条件が発生する。) public class SynchronizedTest5 implements Runnable { public void run() { Calc2 calc2 = new Calc2(); for (int i = 0; i < 10; i++) { calc2.add(i); } } public static void main(String[] args) { SynchronizedTest5 sTest = new SynchronizedTest5(); Thread[] aThread = new Thread[10]; for (int i = 0; i < 10; i++) { aThread[i] = new Thread(sTest); aThread[i].start(); } } } -------------------------------------------------------- -------------------------------------------------------- // synchronizedのインスタンス・メソッドの実行(スレッド・セーフでない) // (複数のスレッドで別々にインスタンスを生成して同時に実行) public class SynchronizedTest6 implements Runnable { public void run() { SynchronizedCalc3 calc3 = new SynchronizedCalc3(); for (int i = 0; i < 10; i++) { calc3.add(i); } } public static void main(String[] args) { SynchronizedTest6 sTest = new SynchronizedTest6(); Thread[] aThread = new Thread[10]; for (int i = 0; i < 10; i++) { aThread[i] = new Thread(sTest); aThread[i].start(); } } } -------------------------------------------------------- これらのSynchronizedTest1〜SynchronizedTest6を実行して、出力結果を確認し synchronizedの働きが予想通りかどうか確認してください。 ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ★ホームページ: http://www.flsi.co.jp/Java_text/ ★このメールマガジンは 「まぐまぐ(http://www.mag2.com)」 を利用して発行しています。 ★バックナンバーは http://www.flsi.co.jp/Java_text/ にあります。 ★このメールマガジンの登録/解除は下記Webページでできます。 http://www.mag2.com/m/0000193915.html ★このメールマガジンへの質問は下記Webページにて受け付けて います。わからない所がありましたら、どしどしと質問をお寄 せください。 http://www.flsi.co.jp/Java_text/ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Copyright (C) 2009 Future Lifestyle Inc. 不許無断複製 |