12章 マルチスレッド
1.スレッドとは
 一つのプロセスで、同時に複数の処理をすることができる。例えば、ブラウザでインターネットからファイルを取得する仕事が動き、さらにユーザからの操作を受け付ける仕事があり・・・これらが同時に動いている。この仕事一つ一つをスレッドという。
複数スレッドが同時に処理することができるプログラムをマルチスレッドプログラムという。
2.スレッドのライフサイクル
 同時にスレッドが動くといっても実際はCPUが一つなので、一つ目のスレッドが動いたら二つ目のスレッドが動く、というように短時間の間に交互に動く。これをタイムディビジョン方式という。
実行状態
現在、実行されているスレッド
待機状態
通信の返事が返ってくるまで待つなどキーボード入力を待ったり、イベントが終了するまで待機している状態。または、何もすることがない状態。
実行可能状態

 本来は、すぐに実行になりたいが別のスレッドが実行中の場合は、一時待つ。
3.スレッドの作成 〜Threadクラス〜
 スレッドを作るには、java.langパッケージのThredクラスを継承すれば作られる。さらに、スレッドの中身はrunメソッドをオーバーライドして記述する。そして、スレッドクラスのstartメソッドを呼び起こせば、新しいスレッドが作成されrunメソッドが別スレッドとして実行される。
Test.java// 独自のスレッドクラス
class MyThread extends Thread{
public  MyThread( ){
}
// このrunメソッドがスレッド本体
public void run( ){
System.out.println(“MyThread Start!”);

// 10回500ミリ秒毎n”Hello”と表示する
    for (int i = 0 ; i < 10 ; i++ ) {
System.out println(“Hello”);
      try{
// sleepメソッドはThreadクラスの静的メソッドでnミリ秒(この場合500ミリ秒)待つ(待機状態)メソッド。
// 例外を投げる可能性があるので、キャッチしないとならない。
Thread.sleep(500);
}catch(Exception e){ }
}
}

public class Test {
public static void main (String args [ ] ) {
// 独自のスレッドクラスのインスタンスを作成
MyThread t = new MyThread(“Thread1”);

// スレッドを開始
t.start( ) ;

// 1秒毎に”Hi !”と10回表示する
for( int i = 0 ; i < 10 ; i++ ) {
System.out.println(“Hi !”) ;
try{
Thread.sleep(1000);
}catch(Exception e){ }
}
}
}

C:\java>javac Test.java

C:\java>java Test
Hi!
MyThread Start!
Hello
Hello
Hi!
Hello
Hello
Hi!
Hello
Hello
Hi!
Hello
Hello
Hi!
MyThread End!
Hi!
Hi!
Hi!
Hi!
C:\java>

4.Threadクラスのメソッド
●コンストラクタ概要
Thread( )
 新しいThreadオブジェクトを割り当てる
Thread(Runnable target )
 新しいThreadオブジェクトを割り当てる
Thread(Runnable target,String name)
 nameという名前の新しいThreadオブジェクトを割り当てる
Thread(String name)
 nameという名前の新しいThreadオブジェクトを割り当てる
●メソッド概要
String
getName( )        
 このスレッドの名前を返す 
boolean     
isAlive( )        
このスレッドが生存しているかどうかを判定する 
void          
join(long millis)  
 このスレッドが終了するのを、最高でmillisミリ秒待機する
void          
run( )                 
このスレッドが別個のRunnable実行オブジェクトを使用して作成された場合、そのRunnableオブジェクトのrunメソッドが呼び出される 
void
setName(String name)
このスレッドを、デーモンスレッドまたはユーザスレッドとしてマークする 
void
setPriority(int newPriority)
このスレッドの優先順位を変更する 
static void
sleep(long millis)
現在実行中のスレッドを指定されたミリ秒数の間、スリープ(一時的に実行を停止)させる
void
start( )
このスレッドの実行を開始 
static void
yield( )
現在実行中のスレッドオブジェクトを一時的に休止させ、ほかのスレッドが実行できるようにする 
5.スレッドの作成 〜Runnableインターフェース〜
Test.java// 独自のクラス
class MyRunnable implements Runnable{
// このrunメソッドがスレッド本体
public void run( ){
System.out.println(“MyRunnable Start!”);

// 10回500ミリ秒毎n”Hello”と表示する
    for (int i = 0 ; i < 10 ; i++ ) {
System.out println(“Hello”);
      try{
// sleepメソッドはThreadクラスの静的メソッドでnミリ秒(この場合500ミリ秒)待つ(待機状態)メソッド。
// 例外を投げる可能性があるので、キャッチしないとならない。
Thread.sleep(500);
}catch(Exception e){ }
}
}

public class Test {
public static void main (String args [ ] ) {
// 独自のスレッドクラスのインスタンスを作成
Runnable r = new MyRunnable( ):
Thread t = new Thread(r);

// スレッドを開始
t.start( ) ;

// 1秒毎に”Hi !”と10回表示する
for( int i = 0 ; i < 10 ; i++ ) {
System.out.println(“Hi !”) ;
try{
Thread.sleep(1000);
}catch(Exception e){ }
}
}
}

C:\java>javac Test.java

C:\java>java Test
Hi!
MyRunnable Start
Hello
Hello
Hi!
Hello
Hello
Hi!
Hello
Hello
Hi!
Hello
Hello
Hi!
MyRunnable End!
Hi!
Hi!
Hi!
Hi!
C:\java>

6.同期
 ロックとは、一つ目のスレッドがあるメソッドをロックすると別のスレッドはそのメソッド内に入れなくなる。そのメソッドを出るときにロックを解除することで別スレッドがそのメソッドに入ることができるようにする。
メソッドを作成するときに「Synchronized」修飾子をつける。javaではこれだけでロックをかける(同期をとる)ことができる。
Test.java// 口座クラス
class Account{
private int balance = 0;

 

 synchronized void deposit ( int amount ){
int a = balance;
    a = a + amount;
    try{
Thread.sleep((int)(Math.random( )*10));
}catch(Exception e){ }
    balance = a;
}
        int getBalance( ){
}
}

// 顧客クラス
class Customer extends Thread{
     Account account;

// コンストラクタ
  Customer(Account account){
this.account = account;
}

// 10円ずつ貯金することを1000回繰り返す
public void run( ) {
          for( int i = 0 ; i < 1000 ; i++ ){
account.deposit(10);
}
}
}

class Test{
public static void main(String args[ ]){
     // 口座を作る
Account account = new Account( );

        // 10人の顧客を作る
Customer customers[ ] = new Customer[10];

        // 10人の顧客が一つの口座に振り込み処理を
   //開始す
for( int i = 0 ; i < 10 ; i++ ){
customer[i] = new Customer(account);
customer[i].start( );

}

        // 10人のスレッドが終わるのを待つ
try{
for( int i = 0 ; < 10 ; i++ ){
customers[i].join( );
}
}catch(Exception e){
System.err.println(“Error:”+e);
}

     // 残高表示
System.out.println(“残高:”+account.getBalance()));
}
}

Test.java// 口座クラス
class Account{
private int balance = 0;

void deposit ( int amount ){
int a = balance;
    a = a + amount;
    try{
Thread.sleep((int)(Math.random( )*10));
}catch(Exception e){ }
    balance = a;
}

        int getBalance( ){
}
}

// 顧客クラス
class Customer extends Thread{
     Account account;

// コンストラクタ
  Customer(Account account){
this.account = account;
}

// 10円ずつ貯金することを1000回繰り返す
public void run( ) {
          for( int i = 0 ; i < 1000 ; i++ ){
account.deposit(10);
}
}
}

class Test{
public static void main(String args[ ]){
     // 口座を作る
Account account = new Account( );

        // 10人の顧客を作る
Customer customers[ ] = new Customer[10];

        // 10人の顧客が一つの口座に振り込み処理を
   //開始する
for( int i = 0 ; i < 10 ; i++ ){
customer[i] = new Customer(account);
customer[i].start( );

}

        // 10人のスレッドが終わるのを待つ
try{
for( int i = 0 ; < 10 ; i++ ){
customers[i].join( );
}
}catch(Exception e){
System.err.println(“Error:”+e);
}

     // 残高表示
System.out.println(“残高:”+account.getBalance()));
}
}

C:\java>javac Test.java

C:\java>java Test
残高:100000C:\java>

C:\java>javac Test.java

C:\java>java Test
残高:10000C:\java>

メソッドにsynchronized修飾子をつけて同期をとる方法ではなく、オブジェクトにロックをかけて同期をとる方法
Test.java// 口座クラス
class Account{
private int balance = 0;

void deposit ( int amount ){
int a = balance;
    a = a + amount;
    try{
Thread.sleep((int)(Math.random( )*10));
}catch(Exception e){ }
    balance = a;
}

        int getBalance( ){
}
}

// 顧客クラス
class Customer extends Thread{
     Account account;

// コンストラクタ
  Customer(Account account){
this.account = account;
}

// 10円ずつ貯金することを1000回繰り返す
public void run( ) {
          for( int i = 0 ; i < 1000 ; i++ ){
sychronized( account ){
account.deposit(10);
}
}
}
}

class Test{
public static void main(String args[ ]){
     // 口座を作る
Account account = new Account( );

        // 10人の顧客を作る
Customer customers[ ] = new Customer[10];

        // 10人の顧客が一つの口座に振り込み処理を開始する
for( int i = 0 ; i < 10 ; i++ ){
customer[i] = new Customer(account);
customer[i].start( );

}

        // 10人のスレッドが終わるのを待つ
try{
for( int i = 0 ; < 10 ; i++ ){
customers[i].join( );
}
}catch(Exception e){
System.err.println(“Error:”+e);
}

     // 残高表示
System.out.println(“残高:”+account.getBalance()));
}
}

C:\java>javac Test.javaC:\java>java Test
残高:100000

C:\java>

 この方法は、accountにロックをかけている。一つ目のスレッドがaccountにロックをかけると、別のスレッドはロックが解除されるまで、synchronizedで囲まれたブロック(synchronizedブロック)には、入ることができない。
7.デットロック
 デットロックとは、マルチスレッドプログラミングで起こりやすいバグの一つ。一つ目のスレッドがXというオブジェクトのロックが解除されるのを待つ。二つ目のスレッドはYというオブジェクトをロックしており、Xというオブジェクトの解放を待つ。このような場合、両方のスレッドはオブジェクトの解放を永遠に待ち続けてしまうため、プログラムは進まなくなり、いわゆる暴走の状態に陥る。このように、複数のスレッドがお互いにロックの解放を永久に待ち続けてしまうことをデットロックという。
Test.java class X{
  int x;
}

class Y{
int y;
}

class A extends Thread{
X x;
Y y;

A(X x, Y y){
this.x = x;
this.y = y;
}

void func1( ){
     System.out.println(“func1:start”);
synchronized(x){
synchronized(y){
try{
sleep(10);
}catch(Exception e){ }
}
}
}

void func2( ) {
System.out println(“func2:start”);
synchronized(y){
synchronized(x){
try{
sleep(10);
}catch(Exception e){ }
}
}
}

public void run( ){
for ( int i = 0 ; i < 1000 ; i++ ){
func1( );
func2( );
}
}
}

class Test{
pubic static void main ( String args[ ]) {
X x = new X( );
Y y = new Y( );

A a[ ] = new A[10];
   for( int i = 0 ; < 10 ; i++ ) {
try{
                 a[i].join( );
}catch(Exception e){ }
}
}
}

C:\java>javac Test.javaC:\java>java Test
func1:start
func1:start
func1:start
func1:start
func1:start
func1:start
func1:start
func1:start
func2:start
func1:start
func1:start
func2:start       ←ここでデットロックが発生

 

 多くの場合、ロックするオブジェクトを同じ順番でロックすれば、デットロックは防げる。例えば上記では、func2のsychronizedでロックする順番をfunc1と同じように「x→y」の順番でロックすればデットロックは防げる。言い換えるとオブジェクトに優先順位を与えるということになる。
このようにマルチスレッド特有のバグが発生した場合の多くは、バグを再現させることすら難しく、デバッグ作業は大変な作業になってしまう。バグが起きないようにあらかじめちゃんと設計してからプログラミングする必要がある。
8.スレッド間の通信
 スレッドから一時的にロックを解放し、他のスレッドにsynchronizedメソッドもしくはsynchronizedブロックを実行するチャンスを与えることができる。例えば、一つ目のスレッドはメッセージを受信するスレッドで、二つ目のスレッドはメッセージを与えるスレッドである。メッセージ受信のスレッドは、メッセージボックスにメッセージがなければメッセージが格納されるまですることがない。このような場合ほかのスレッド(メッセージ送信スレッド)に制御を移す。逆にメッセージ送信スレッドは、メッセージボックスがいっぱいならば、メッセージ受信スレッドがメッセージを処理し終わるまで待たなければならない。
このように、他のスレッドが処理し終わるまで待つ場合は「wait」メソッドを使用し、他のスレッドに制御を与える場合は「notify」または「notifyAll」メソッドを使用する。notifyAllメソッドはwaitしているすべてのメソッドに対して通知をするが、notifyメソッドは、いずれか一つのスレッドに対してのみ通知する。どのスレッドに通知されるかは、JVMが勝手に決めていいことになっている。
Test.java import java.util.*;

// メッセージボックス
class MessageBox{
     private String message;

// メッセージボックスにメッセージを入れる
synchronized void  messageIn(String msg){
try{
// メッセージがすでに入っていれば、なくなるまで待つ
while( message != null){
wait( );
}

// メッセージを入れる
message = msg;

// メッセージを入れ終わったら別のスレッドを起こす
notifyAll( );

// 以下例外処理
}catch(Exception e){
System.err.println(“messageIn:Error:”+e);
System.exit(1);
}
}

// メッセージ取り出し
synchronized String messageOut( ){
try {
// メッセージがなければ、メッセージが入るまで待つ
while(message == null){
wait( );
}

// メッセージ取り出し
String s == message;
message = null ;

// 取り出したら別のスレッドを起こす
notifyAll( );
return s ;
// 以下例外処理
} catch (Exception e ){ }
System.err.println(“messageOut:Error”);
System.exit( 1);
return “”;
}
}

// 以下例外処理
class MessageSender extends Thread{
public void run( ){
for( int i = 0 ; i < 100 ; i++ ){
Test.msgBox.messageIn(“”+Math.random( ));
          }
}
}

// メッセージを取り出し、表示するスレッド
class MessageReciever extends Thread {
public void run ( ){
          for ( int i = 0 ; i < 100 ; i++ ){
System.out.println(Test.msgBox.messageOut( ));
}
}
}

class Test{
public static MessageBox msgBox = new MessageBox( );

public static  void main(String args[ ]){
MessageReciever reciever = new MessageReciever( );
reciever.start( );

MessageSender sender = new MessageSender( );
sender.start( );

try{
sender.join( ;
reciever.join( );
}catch(Exception e){ }
}
}

C:\java>javac Test.javaC:\java>java Test
0.2894081823761079
0.20714542091971344

中略

0.519766868805261
0.6181891980702379

C:\java>