スレッドの実際 -- Pthread と Java

オペレーティングシステム II

                                       電子・情報工学系
                                       新城 靖 (臨時代講)
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2004/2004-12-14
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/coins/
http://www.is.tsukuba.ac.jp/~yas/index-j.html

■スレッドの実際

◆スレッドとは

スレッド(thread) あるいは、 軽量プロセス(lightweight processes) とは、 1つの保護の単位としての プロセス(タスク,あるいは,アドレス空間) 内部にふくまれている論理的な並列処理の単位。

シングルスレッドのプログラム
1度に1つの手続き(Cの関数)しか動かない。
マルチスレッドのプログラム
1度にスレッドの数だけの手続きが論理的には同時に動く。 (同期でブロックされているものも含む)

図? シングルスレッドのプロセスとマルチスレッドのプロセス

図? シングルスレッドのプロセスとマルチスレッドのプロセス

軽量プロセスというと、内部にループを含むような語感がある。

サブルーチンと比較可能。

◆スレッドの利用目的

◆スレッドの内容

個々のスレッドごとに持つもの プロセス全体で共有

■Pthread

Pthread は、POSIX 1003.1c-1995という標準に準拠したスレッド・ライブラリ。 POSIX Thread とも呼ばれる。

◆Pthreadを利用したプログラムのコンパイル

Pthread を利用したプログラムを書く時には、次のようなヘッダ・ファイルを 読み込む。


#include <pthread.h>

SGI (O2, Origin), Linux でリンク。-lpthread を付ける。

% cc -o create create.c -lpthread [←]
% ./create [←]
...
% []
FreeBSD でのリンク。-lc_r を付ける。
% cc -o create create.c -lc_r [←]
% ./create [←]
...
% []
FreeBSD では、次のオプションが使えるものもある。
% cc -pthread -o create create.c [←]
% ./create [←]
...
% []

Solaris (Unix International系)でのコンパイルとリンク。 -D_REENTRANT-lpthread を付ける。

% cc -D_REENTRANT -o create create.c -lpthread [←]
% ./create [←]
...
% []
セマフォを使う時、SunOS 5.6 では、リンク時に -lposix4 オプショ ンを付ける。SunOS 5.7, 5.8 では、リンク時に -lrt オプションを付 ける。

■スレッドの生成・消滅

スレッドは、普通のプログラムの、 サブルーチン(C言語の関数、手続き) に近い。 サブルーチンの場合,呼び出すと、呼び出された方が動き、自分自身は,止ま る。スレッドでは,新たにスレッドを生成した場合,生成した方と生成さ れた方は,論理的には2つとも同時に動く。

■fork-joinモデル

図? fork-joinモデルの実現

図? fork-joinモデルの実現

  1. 逐次処理(スレッド/プロセスが1つ)の状態から始まる
  2. 並列性が必要になった時、fork命令で複数のスレッド/プロセスに分か れて並列処理を行う。
  3. 並列に動作できる部分が終ると join 命令で再び逐次処理に戻る。

◆Unixのfork

fork() システムコールでコピーが作られる。 join の代わりに、子どもは exit()、親は wait()。

◆Pthreadはcreate

Pthread では、コピーではなく create で新たにスレッドを作る。同じ関数を 実行したい時には、直接 call する。(別の関数を実行するなら呼ばなくても よい。) 子スレッドでは、pthread_exit() (トップの 手続きからリターン)、 親は、pthread_join() する。

後で join する必要がない時には、pthread_detach() を使って切り離す。 (joinしなくてもゾンビが残らない。)

◆例スレッドの生成とjoin

   1: 
   2: /*
   3:         create-join-rnd.c -- スレッドを2つ作るプログラム
   4:         Start: 2002/02/02 20:00:25
   5: */
   6: 
   7: #include <pthread.h>
   8: #include <stdlib.h>     /* lrand48() */
   9: #include <time.h>       /* time() */
  10: #include <sys/types.h>  /* time() */
  11: 
  12: void func1( int x );
  13: void func2( int x );
  14: 
  15: main()
  16: {
  17:     pthread_t t1 ;
  18:     pthread_t t2 ;
  19:         srand48( time(0) );
  20:         pthread_create( &t1, NULL, (void *)func1, (void *)10 );
  21:         pthread_create( &t2, NULL, (void *)func2, (void *)20 );
  22:         printf("main()\n");
  23:         pthread_join( t1, NULL );
  24:         pthread_join( t2, NULL );
  25: }
  26: 
  27: void func1( int x )
  28: {
  29:     int i ;
  30:         for( i = 0 ; i<3 ; i++ )
  31:         {
  32:             sleep_rand();
  33:             printf("func1( %d ): %d \n",x, i );
  34:         }
  35: }
  36: 
  37: void func2( int x )
  38: {
  39:     int i ;
  40:         for( i = 0 ; i<3 ; i++ )
  41:         {
  42:             sleep_rand();
  43:             printf("func2( %d ): %d \n",x, i );
  44:         }
  45: }
  46: 
  47: sleep_rand()
  48: {
  49:     long rnd ;
  50:     struct timespec req ;
  51:         rnd = lrand48();
  52:         req.tv_sec = 0 ;
  53:         req.tv_nsec = rnd ;
  54:         nanosleep( &req, 0 );
  55: }
  56: 

実行例。
% cp ~yas/classes/os2-2004/create-join-rnd.c . [←]
% gcc -o create-join-rnd create-join-rnd.c -lpthread [←]
% ./create-join-rnd  [←]
main()
func2( 20 ): 0 
func2( 20 ): 1 
func2( 20 ): 2 
func1( 10 ): 0 
func1( 10 ): 1 
func1( 10 ): 2 
% ./create-join-rnd [←]
main()
func1( 10 ): 0 
func1( 10 ): 1 
func1( 10 ): 2 
func2( 20 ): 0 
func2( 20 ): 1 
func2( 20 ): 2 
% ./create-join-rnd [←]
main()
func2( 20 ): 0 
func1( 10 ): 0 
func2( 20 ): 1 
func1( 10 ): 1 
func1( 10 ): 2 
func2( 20 ): 2 
% []
この例では、次の3つのスレッドが作られる。
  1. main を実行しているスレッド
  2. func2 から作られたスレッド t2
  3. func1 から作られたスレッド t1
マルチスレッド・プログラミングでは、main関数もまた1つのスレッドが実行 していると考える。これを 初期スレッド 、あるいは、 メインスレッド とよぶ。Pthread では、 メインスレッド以外のスレッドは、 pthread_create() により作られる。

どういう順序で実行されるかは、決まっていない。 決まっていない。スレッドは、もともと順番を決めないような処理、 非同期的(asynchronous) な処理を表現するためのもの。どうしても他のスレッドと同期を行なう必要が 出てきた時には、mutex や条件変数といった同期機能を使う。

pthread_create()で指定された関数からリターンすると、そ のスレッドが終了する。pthread_exit() を呼び出してもよい。 ただし、 初期スレッド が終了すると、プロセス全体が終了する。 exit() システムコールを呼び出しても終了する。

■echoサーバを pthread を使って書き換える

複数のクライアントの処理を並列に行うために、スレッドを使う。 複数の入出力(プロセス間通信)を同時に行うため。

◆echo-server-nofork-fdopen.c

   1:	
   2:	/*
   3:	        echo-server-nofork-fdopen.c -- 受け取った文字列をそのまま返すサーバ(fork無し版)
   4:	        ~yas/syspro/ipc/echo-server-nofork-fdopen.c
   5:	        Created on 2004/05/09 19:08:47
   6:	*/
   7:	#define _USE_BSD        /* wait4() */
   8:	#include <stdio.h>
   9:	#include <sys/types.h>  /* socket(), wait4() */
  10:	#include <sys/socket.h> /* socket() */
  11:	#include <netinet/in.h> /* struct sockaddr_in */
  12:	#include <sys/resource.h> /* wait4() */
  13:	#include <sys/wait.h>   /* wait4() */
  14:	#include <netdb.h>      /* getnameinfo() */
  15:	
  16:	extern  void echo_server( int portno );
  17:	extern  void echo_reply( int com );
  18:	extern  void print_my_host_port( int portno );
  19:	extern  void tcp_peeraddr_print( int com );
  20:	extern  void sockaddr_print( struct sockaddr *addrp, int addr_len );
  21:	extern  tcp_acc_port( int portno );
  22:	extern  int fdopen_sock( int sock, FILE **inp, FILE **outp );
  23:	
  24:	main( int argc, char *argv[] )
  25:	{
  26:	    int portno ;
  27:	        if( argc >= 3 )
  28:	        {
  29:	            fprintf( stdout,"Usage: %s [portno] \n",argv[0] );
  30:	            exit( -1 );
  31:	        }
  32:	        if( argc == 2 )
  33:	            portno = strtol( argv[1],0,10 );
  34:	        else
  35:	            portno = getuid();
  36:	        echo_server( portno );
  37:	}
  38:	
  39:	void
  40:	echo_server( int portno )
  41:	{
  42:	    int acc,com ;
  43:	        acc = tcp_acc_port( portno );
  44:	        if( acc<0 )
  45:	            exit( -1 );
  46:	        print_my_host_port( portno );
  47:	        while( 1 )
  48:	        {
  49:	            if( (com = accept( acc,0,0 )) < 0 )
  50:	            {
  51:	                perror("accept");
  52:	                exit( -1 );
  53:	            }
  54:	            tcp_peeraddr_print( com );
  55:	            echo_reply( com );
  56:	        }
  57:	}
  58:	
  59:	#define BUFFERSIZE      1024
  60:	
  61:	void
  62:	echo_reply( int com )
  63:	{
  64:	    char line[BUFFERSIZE] ;
  65:	    int rcount ;
  66:	    int wcount ;
  67:	    FILE *in, *out ;
  68:	
  69:	        if( fdopen_sock(com,&in,&out) < 0 )
  70:	        {
  71:	            fprintf(stderr,"fdooen()\n");
  72:	            exit( 1 );
  73:	        }
  74:	        while( fgets(line,BUFFERSIZE,in) )
  75:	        {
  76:	            rcount = strlen( line );
  77:	            printf("[%d] received (fd==%d) %d bytes, [%s]\n",getpid(),com,rcount,line );
  78:	            fflush( stdout );
  79:	            fprintf(out,"%s",line );
  80:	        }
  81:	        printf("[%d] connection (fd==%d) closed.\n",getpid(),com );
  82:	        fclose( in );
  83:	        fclose( out );
  84:	}
  85:	
...
  87:	print_my_host_port( int portno )
...
  96:	tcp_peeraddr_print( int com )
...
 112:	sockaddr_print( struct sockaddr *addrp, int addr_len )
...
 122:	tcp_acc_port( int portno )

サーバ側。サーバは、終了しないので、最後に、^CDel を押して、割り込みを掛けて終了させる。

注意:全員がポート番号 1231 を使うとプログラムが動かないことがある。


% cp ~yas/syspro/ipc/echo-server-nofork-fdopen.c . [←]
% make echo-server-nofork-fdopen [←]
cc     echo-server-nofork-fdopen.c   -o echo-server-nofork-fdopen
% ./echo-server-nofork-fdopen 1231 [←]
run telnet adonis9.coins.tsukuba.ac.jp 1231 
[6419] connection (fd==4) from 130.158.86.28:47329
[6419] received (fd==4) 5 bytes, [123
]
[6419] received (fd==4) 5 bytes, [456
]
[6419] received (fd==4) 5 bytes, [790
]
[6419] connection (fd==4) closed.
^C
% []
クライアント側
% telnet adonis9.coins.tsukuba.ac.jp 1231  [←]
Trying 130.158.86.29...
Connected to adonis9.coins.tsukuba.ac.jp.
Escape character is '^]'.
123[←]
123
456[←]
456
790[←]
790
^][←]
telnet> quit[←]
Connection closed.
% []
複数のクライアントに対してサービスの同時に提供することが望ましい。 echo-server-nofork-fdopen.c は、1つのクライアントからの接続されると、そのクライアントに掛り切りに なって、他のクライアントにはサービスを提供できないという問題がある。 標準の echo サーバ(ポート番号 7 で動作している) は、複数のクライアント から接続されれた場合、同時にサービスを提供することができる。

複数のクライアントに対してサービスの同時に提供するには次のような方法が ある。

◆echo-server-pthread.c

再掲。詳細は、 システムプログラムのページ参照。
   1:	
   2:	/*
   3:	        echo-server-pthread.c -- 受け取った文字列をそのまま返すサーバ(pthread版)
   4:	        ~yas/syspro/ipc/echo-server-pthread.c
   5:	        Created on 2002/02/04 21:17:04
   6:	*/
   7:	#include <stdio.h>
   8:	#include <sys/types.h>  /* socket(), time() */
   9:	#include <sys/socket.h> /* socket() */
  10:	#include <netinet/in.h> /* struct sockaddr_in */
  11:	#include <pthread.h>    /* pthread_create */
  12:	#include <netdb.h>      /* getnameinfo() */
  13:	
  14:	extern  void echo_server( int portno );
  15:	extern  void echo_reply( int com );
  16:	extern  void print_my_host_port( int portno );
  17:	extern  void tcp_peeraddr_print( int com );
  18:	extern  void sockaddr_print( struct sockaddr *addrp, int addr_len );
  19:	extern  tcp_acc_port( int portno );
  20:	extern  int fdopen_sock( int sock, FILE **inp, FILE **outp );
  21:	
  22:	main( int argc, char *argv[] )
  23:	{
  24:	    int portno ;
  25:	        if( argc >= 3 )
  26:	        {
  27:	            fprintf( stdout,"Usage: %s [portno] \n",argv[0] );
  28:	            exit( -1 );
  29:	        }
  30:	        if( argc == 2 )
  31:	            portno = strtol( argv[1],0,10 );
  32:	        else
  33:	            portno = getuid();
  34:	        echo_server( portno );
  35:	}
  36:	
  37:	void
  38:	echo_server( int portno )
  39:	{
  40:	    int acc,com ;
  41:	    pthread_t worker ;
  42:	        acc = tcp_acc_port( portno );
  43:	        if( acc<0 )
  44:	            exit( -1 );
  45:	        print_my_host_port( portno );
  46:	        while( 1 )
  47:	        {
  48:	            if( (com = accept( acc,0,0 )) < 0 )
  49:	            {
  50:	                perror("accept");
  51:	                exit( -1 );
  52:	            }
  53:	            tcp_peeraddr_print( com );
  54:	            if( pthread_create( &worker, NULL, (void *)echo_reply, (void *)com)
  55:	                != 0 )
  56:	            {
  57:	                perror("pthread_create()");
  58:	                exit( 1 );
  59:	            }
  60:	            pthread_detach( worker );
  61:	        }
  62:	}
  63:	
違い
echo-server-nofork-fdopen.c
  55:	            echo_reply( com );

echo-server-pthread.c
  54:	            if( pthread_create( &worker, NULL, (void *)echo_reply, (void *)com)
  55:	                != 0 )
  56:	            {
  57:	                perror("pthread_create()");
  58:	                exit( 1 );
  59:	            }
  60:	            pthread_detach( worker );
特徴
% ./echo-server-pthread 1231 [←]
run telnet adonis9.coins.tsukuba.ac.jp 1231 
[7298] connection (fd==4) from 130.158.86.28:47333
[7300] received (fd==4) 5 bytes, [012
]
[7298] connection (fd==8) from 127.0.0.1:42170
[7302] received (fd==8) 5 bytes, [abc
]
[7302] received (fd==8) 5 bytes, [def
]
[7302] connection (fd==8) closed.
[7300] received (fd==4) 5 bytes, [345
]
[7300] connection (fd==4) closed.
^C
% []
PID が本来は、同じはずである。しかし、Linux では、違う。 ファイル記述子は、同じプロセス内で共有される。 (Solaris (vine1)で試してみなさい。)

クライアント側(その1)。

% telnet adonis9.coins.tsukuba.ac.jp 1231 [←]
Trying 130.158.86.29...
Connected to adonis9.coins.tsukuba.ac.jp.
Escape character is '^]'.
012[←]
012
345[←]
345
^]
telnet> quit[←]
Connection closed.
% []
クライアント側(その2)。
% telnet adonis9.coins.tsukuba.ac.jp 1231 [←]
Trying 130.158.86.29...
Connected to adonis9.coins.tsukuba.ac.jp.
Escape character is '^]'.
abc[←]
abc
def[←]
def
^]
telnet> quit[←]
Connection closed.
% []

■Pthreadのスケジューリング

基本的には優先順位式スケジューリングだが、実時間(real time)とそうでな いもの(other)に大別させる。
SCHED_OTHER
時分割(Timesharing)
SCHED_FIFO
実時間、First-In-First-Out
SCHED_RR
実時間、Round-Robin
実時間の方が、TSSより優先順位が高い。 Linux は、実時間オペレーティング・システムではないので、 SCHED_FIFO や SCHED_RR が定義されているが、実際に 動作する保証はない。

(実時間スレッドの)優先順位の設定

優先順位継承方式(priority inheritance protocol)。 優先順位逆転をさける。

優先順位の低いスレッドが確保したロックを優先順位が高いスレッドが確保す るのを試みてブロックされる。

■Javaのスレッド

プログラミング言語に最初から組込まれている。

◆Javaはcreate

Java では、コピー(Unix の fork() 流)ではなく create で新たにスレッドを 作る。

◆スレッドの生成とjoin

   1: 
   2: /*
   3:  * ThreadCreateJoin.java -- スレッドを2つ作るプログラム
   4:  * Start: 2002/02/04 22:08:12
   5: */
   6: 
   7: class ThreadA implements Runnable
   8: {
   9:     int x ;
  10:     java.util.Random rand ;
  11:     ThreadA( int arg_x )
  12:     {
  13:         x = arg_x ;
  14:         rand = new java.util.Random();
  15:     }
  16:     public void run()
  17:     {
  18:         int i ;
  19:         for( i=0 ; i<3 ; i++ )
  20:         {
  21:             sleep_rand();
  22:             System.out.println("ThreadA("+x+").run(): "+i);
  23:         }
  24:     }
  25:     void sleep_rand()
  26:     {
  27:         int sleep = rand.nextInt() % 1000 ;
  28:         if( sleep < 0 ) sleep = -sleep ;
  29:         Thread t1 = Thread.currentThread();
  30:         try
  31:         {
  32:             t1.sleep( sleep );
  33:         }
  34:         catch( java.lang.InterruptedException e )
  35:         {
  36:         }
  37:     }
  38: }
  39: 
  40: class ThreadB implements Runnable
  41: {
  42:     int x ;
  43:     java.util.Random rand ;
  44:     ThreadB( int arg_x )
  45:     {
  46:         x = arg_x ;
  47:         rand = new java.util.Random();
  48:     }
  49:     public void run()
  50:     {
  51:         int i ;
  52:         for( i=0 ; i<3 ; i++ )
  53:         {
  54:             sleep_rand();
  55:             System.out.println("ThreadB("+x+").run(): "+i);
  56:         }
  57:     }
  58:     void sleep_rand()
  59:     {
  60:         int sleep = rand.nextInt() % 1000 ;
  61:         if( sleep < 0 ) sleep = -sleep ;
  62:         Thread t1 = Thread.currentThread();
  63:         try
  64:         {
  65:             t1.sleep( sleep );
  66:         }
  67:         catch( java.lang.InterruptedException e )
  68:         {
  69:         }
  70:     }
  71: }
  72: 
  73: class ThreadCreateJoin
  74: {
  75:     static void main(String argv[])
  76:     {
  77:         Thread t1 = new Thread( new ThreadA(10) );
  78:         t1.start();
  79:         Thread t2 = new Thread( new ThreadB(10) );
  80:         t2.start();
  81:         System.out.println("main()");
  82:         try
  83:         {
  84:             t1.join();
  85:             t2.join();
  86:         }
  87:         catch( InterruptedException e )
  88:         {
  89:             System.err.println("main(): Interrupted");
  90:         }
  91:     }
  92: }

実行例。

% cp ~yas/classes/os2-2004/ThreadCreateJoin.java . [←]
% javac ThreadCreateJoin.java [←]
% java ThreadCreateJoin [←]
main()
ThreadA(10).run(): 0
ThreadB(10).run(): 0
ThreadB(10).run(): 1
ThreadA(10).run(): 1
ThreadA(10).run(): 2
ThreadB(10).run(): 2
% java ThreadCreateJoin [←]
main()
ThreadA(10).run(): 0
ThreadB(10).run(): 0
ThreadB(10).run(): 1
ThreadA(10).run(): 1
ThreadA(10).run(): 2
ThreadB(10).run(): 2
% java ThreadCreateJoin [←]
main()
ThreadA(10).run(): 0
ThreadA(10).run(): 1
ThreadB(10).run(): 0
ThreadA(10).run(): 2
ThreadB(10).run(): 1
ThreadB(10).run(): 2
% []

■echoサーバ(Java版)

再掲。詳細は、 システムプログラムのページ参照。
   1:	/*
   2:	        EchoServerSingle.java -- 文字列を送受信するサーバ(TCP/IP, Java版, スレッドなし)
   3:	        ~yas/syspro/ipc/EchoServer.java
   4:	        Created on 2004/05/09 20:00:24
   5:	*/
   6:	
   7:	import java.net.*;
   8:	import java.io.*;
   9:	
  10:	class EchoServerSingle
  11:	{
  12:	    public static void main(String argv[]) throws IOException {
  13:	        if( argv.length != 1 )
  14:	        {
  15:	            System.err.println("Usage: % java EchoServer port");
  16:	            System.exit( -1 );
  17:	        }
  18:	        int portno = Integer.parseInt( argv[0] );
  19:	        echo_server( portno );
  20:	    }
  22:	    public static void echo_server( int portno ) throws IOException
  23:	    {
  24:	        ServerSocket acc = new ServerSocket( portno );
  25:	        print_my_host_port( portno );
  26:	        while( true )
  27:	        {
  28:	            Socket com = acc.accept();
  29:	            tcp_peeraddr_print( com );
  30:	            EchoServerWorker esw = new EchoServerWorker(com);
  31:	            esw.run();
  32:	        }
  33:	    }
<省略>

   1:	/*
   2:	        EchoServerWorker.java -- 文字列を送受信するサーバ/ワーカ(TCP/IP, Java版)
   3:	        ~yas/syspro/ipc/EchoServerWorker.java
   4:	*/
   5:	
   6:	import java.net.*;
   7:	import java.io.*;
   8:	
   9:	public class EchoServerWorker implements Runnable
  10:	{
  11:	    Socket com ;
  12:	    EchoServerWorker( Socket com )
  13:	    {
  14:	        this.com = com ;
  15:	    }
  16:	    public void run()
  17:	    {
  18:	        try
  19:	        {
  20:	            BufferedReader in = new BufferedReader(
  21:	                new InputStreamReader( com.getInputStream() ));
  22:	            PrintStream out = new PrintStream( com.getOutputStream() );
  23:	            String line;
  24:	            while( (line = in.readLine())!= null )
  25:	            {
  26:	                stdout.println("received (hash=="+com.hashCode()+") "+
  27:	                               line.length()+" characters, ["+line+"]");
  28:	                out.println( line );
  29:	            }
  30:	            stdout.println("connection (hash=="+com.hashCode()+") closed.");
  31:	            in.close();
  32:	            out.close();
  33:	            com.close();
  34:	        }
  35:	        catch( IOException e )
  36:	        {
  37:	            stderr.println( e );
  38:	        }
  39:	    }
  40:	    static java.io.BufferedReader stdin = 
  41:	        new java.io.BufferedReader( new java.io.InputStreamReader(System.in) );
  42:	    static java.io.PrintStream stdout = System.out;
  43:	    static java.io.PrintStream stderr = System.err;     
  44:	}

◆スレッドによる複数のクライアントに対するサービスの同時提供(Java版)

わずかな修正で、スレッド対応になり、複数のクライアントに対してサービス を同時に提供できるようになる。
   1:	/*
   2:	        EchoServer.java -- 文字列を送受信するサーバ(TCP/IP, Java版)
   3:	        ~yas/syspro/ipc/EchoServer.java
   4:	        Created on 2004/02/14 16:22:13
   5:	*/
   6:	
   7:	import java.net.*;
   8:	import java.io.*;
   9:	
  10:	class EchoServer
  11:	{
  12:	    public static void main(String argv[]) throws IOException {
  13:	        if( argv.length != 1 )
  14:	        {
  15:	            System.err.println("Usage: % java EchoServer port");
  16:	            System.exit( -1 );
  17:	        }
  18:	        int portno = Integer.parseInt( argv[0] );
  19:	        echo_server( portno );
  20:	    }
  21:	
  22:	    public static void echo_server( int portno ) throws IOException
  23:	    {
  24:	        ServerSocket acc = new ServerSocket( portno );
  25:	        print_my_host_port( portno );
  26:	        while( true )
  27:	        {
  28:	            Socket com = acc.accept();
  29:	            tcp_peeraddr_print( com );
  30:	            Thread th = new Thread( new EchoServerWorker(com) );
  31:	            th.start();
  32:	        }
  33:	    }
<省略>
違いは、次の部分だけである。
EchoServerSingle.java
  30:	            EchoServerWorker esw = new EchoServerWorker(com);
  31:	            esw.run();
EchoServer.java
  30:	            Thread th = new Thread( new EchoServerWorker(com) );
  31:	            th.start();

■Java Appletのスレッド

アニメーションを実現するために、スレッドを使う。

◆Javaアプレットのタグ

Javaアプレットは、HTML からみると、 インライン・イメージと似ている。 Javaアプレットのためのタグは、 <APPLET></APPLET>

HTML記述:

<APPLET CODE="rctext.class" WIDTH="500" HEIGHT="50">
<PARAM NAME="message" VALUE="hello,world">
与えたメッセージが動き回るJavaアプレット。
</APPLET>
表示例:

与えたメッセージが動き回るJavaアプレット。

CODE属性で、Javaアプレットを保存している ファイルのURL(ただし、同一ホスト内)、 WIDTH属性で、幅、 HEIGHT属性で、高さを指定する。 <PARAM>タグを使えば、アプレットに オプションを渡すことがでる。

<APPLET></APPLET>の間には、 Java Applet に対応していないブラウザのためのテキストを書く。

◆アプレットの手続き(イベント)

いくつかのイベントを自分自身でオーバーライドする。
init()
初期化。load時、または、reload時に呼ばれる。
start()
init() の次に呼ばれる。中断後も呼ばれる。たとえば、他のページから 戻ってきた時など。init() と違い、何度も呼ばれることがある。
stop()
アプレットを含むページから別のページに移る時に呼ばれる。 資源の節約のために処理を止める。
destroy()
アプレットの終了直前に呼ばれる。
pain()
描画を行う。java.awt.Graphics を取る。 隠れていたウインドウが表に出たときなどに呼ばれる。(アニメーションを実 現したいと思っても、自動的には呼び出されない。)
アニメーションを実現するには、定期的にに repaint() メソッドを呼ぶ。す ると、ブラウザは、アプレットの update()を呼ぶ。update()は、paint() メ ソッドを呼ぶ。結果的に、paint() メソッドが定期的に呼ばれる。

◆rctext.java

   1: //
   2: //  rctext.java -- rolling color text demo by Y.Shinjo <yas@is.tsukuba.ac.jp>
   3: //  Start: 1997/06/18 17:45:00
   4: //
   5: 
   6: import java.awt.* ;
   7: 
   8: public class rctext extends java.applet.Applet implements Runnable 
   9: {
  10:     volatile Thread thread ;
  11:     int fontsize = 24 ;
  12:     Font font ;
  13:     Color color[] ;
  14:     int sleep = 100 ;
  15:     int step = 5 ;
  16:     int x, y, n ;
  17:     String message ;
  18: 
  19:     public void init()
  20:     {
  21:         font = new java.awt.Font("TimesRoman",Font.PLAIN, fontsize );
  22:         message = getParameter("message");
  23:         if( message == null )
  24:             message="null" ;
  25:         x = 0 ; y = 0 ; n = 0 ;
  26:         color = new Color[6] ;
  27:         color[0] = Color.green ;
  28:         color[1] = Color.yellow ;
  29:         color[2] = Color.orange ;
  30:         color[3] = Color.red ;
  31:         color[4] = Color.magenta ;
  32:         color[5] = Color.blue ;
  33:     }
  34: 
  35:     public void paint(Graphics g)
  36:     {
  37:         g.setFont( font );
  38:         g.setColor( getBackground() );
  39:         g.drawString( message, x, y );
  40:         x += step ;
  41:         if( x > size().width )
  42:         {
  43:             FontMetrics fm = g.getFontMetrics();
  44:             x = - fm.stringWidth( message );
  45:         }
  46:         y += step ;
  47:         if( y > size().height + fontsize )
  48:         {
  49:             y = 0 ;
  50:             n ++ ;
  51:             if( n>= color.length )
  52:                 n = 0 ;
  53:         }
  54:         g.setColor( color[n] );
  55:         g.drawString( message, x, y );
  56:     }
  57: 
  58:     public void start()
  59:     {
  60:         thread = new Thread(this);
  61:         thread.start();
  62:     }
  63: 
  64:     public void stop()
  65:     {
  66:         thread = null;
  67:     }
  68: 
  69:     public void run()
  70:     {
  71:         Thread thisThread = Thread.currentThread();
  72:         while( thread == thisThread )
  73:         {
  74:             try
  75:             {
  76:                 thisThread.sleep( sleep ) ;
  77:             }
  78:             catch( InterruptedException e )
  79:             {
  80:                 stop();
  81:             }
  82:             repaint();
  83:         }
  84:     }
  85: }

◆スレッドなしの例

    public void start()
    {
	Thread thisThread = Thread.currentThread();
	while( thread == thisThread )
	{
	    try
	    {
		thisThread.sleep( sleep ) ;
	    }
	    catch( InterruptedException e )
	    {
		stop();
	    }
	    repaint();
	}
    }

start() からリターンしないので、画面には何も表示されない。

■Javaのスレッドのスケジューリング

優先順位式スケジューリング。最も優先順位が高い、実行可能状態のスレッド が、(最大で)CPU の個数だけ同時に実行される。 優先順位が高いスレッドが実行可能になると、優先順位が低いスレッドから CPU を横取り可能(preempt)して割り当てる。

スレッドの状態

初期状態。
コンストラクタで作らてから start() されるまで。
実行可能状態
start() が呼び出されて、他のイベントを待っていない状態。
ブロック状態
他のイベントが起きるのを待っている状態。
終了状態
run() メソッドが終了した状態。

優先順位

Thread.MAX_PRIORITY
最高優先順位
Thread.MIN_PRIORITY
最低優先順位
Thread.NORM_PRIORITY
デフォルトの優先順位

優先順位の設定

スケジューリングが行われる(スレッドが切替えられる可能性がある)イベント

これ以外の時には、スケジューリングが行われない。タイマは、プラットホー ムによっては使われないことがいる。

◆デーモン・スレッド

スレッドは、次の2つに分類される。 全てのユーザ・スレッドが終了したら、仮想計算機を終了する。

■スレッドの難しさ

同期がでてきたら、非常に難しい。

スレッドを殺したり外から強制的に止めたりすると、地獄に落ちる。

◆古い機能

Javaの次の機能は古いので使ってはいけない。 stop() されると、ロックが解除され、怪しい中間状態のオブジェクトが残る。 PThread には、スレッドを殺すという命令は存在しない。その代りに cancel という機能があり、特定の場所(怪しい中間状態ではないことが 保証された場所)でのみスレッドを終了させることができる。

suspend(), resume() は、清く正しいプログラムには不要である。 多くの場合、wait(), notify() で書ける。