Javaの再起動可能スレッドとスレッドプールクラスの設計(詳細説明)

高洛峰
リリース: 2017-01-23 16:18:05
オリジナル
1441 人が閲覧しました

JAVA マルチスレッド プログラミングを知っている人なら誰でも、スレッドを生成するには 2 つの方法があることを知っています。1 つは、クラスが Thread クラスを直接継承し、その run() メソッドを実装する方法です。もう 1 つは、クラスが Runnable インターフェイスを実装し、 run() メソッドを使用して、次の形式のように、このクラスをコンストラクター パラメーターとして使用して新しいスレッドを作成します: Thread t=new Thread(myRunnable)。最後に、Thread クラスの start() メソッドを実行することでスレッドが開始されます。

JAVA では、スレッドの実行が終了すると、つまり run() メソッドを実行した後は再起動できません。この時点で、このスレッド オブジェクトは不要なオブジェクトとなり、ガベージ コレクターによるリサイクルを待っています。次回このスレッドを再度開始する場合は、新しいスレッド オブジェクトを作成して再度開始する必要があります。オブジェクトの頻繁な作成と破棄は、操作効率に影響を与えるだけでなく、不要なスレッド オブジェクトをリサイクルする時間がないため、大量のガベージ メモリが生成される可能性があります。この影響は、ストレージ容量と処理速度が比較的限られているモバイル プラットフォームでは特に大きくなります。 。では、オブジェクトを頻繁に作成したり破棄したりせずに繰り返し開始できるようにスレッド クラスを再設計することはできるでしょうか?

もちろんです。次に、この「再開可能スレッド」クラスの設計を紹介します。

まず第一に、スレッドに実行させたいタスクをスレッドの run() メソッドに直接入れても、とにかく目的を達成できないことを明確にする必要があります。 JAVA スレッド クラスが実行されると、 run() メソッドが完了すると、再度開始することはできません。したがって、唯一の実現可能な方法は、ユーザー プログラム (「ユーザー プロセス」と呼ばれる場合もあります) の run() メソッドを、ユーザー プロセスが実行されるときにスレッドの実際の run() メソッド内の while ループ本体に入れることです。待って 。スレッドを再起動するために restart メソッドが呼び出されると、待機中のスレッドが実際に起動されて、次の while ループが開始されます。大まかな考え方が決まり、次のコードは理解しやすいです:

public class ReusableThread implements Runnable {
 //线程状态监听者接口
 public interface ThreadStateListener {
  public abstract void onRunOver(ReusableThread thread);//当用户过程执行完毕后调用的方法
 }
  
 public static final byte STATE_READY=0; //线程已准备好,等待开始用户过程
 public static final byte STATE_STARTED=1; //用户过程已启动
 public static final byte STATE_DESTROYED=2; //线程最终销毁
  
 byte mState; //标示可重启线程的当前状态
  
 Thread mThread; //实际的主线程对象
 Runnable mProc; //用户过程的run()方法定义在mProc中
 ThreadStateListener mListener; //状态监听者,可以为null
  
 /** Creates a new instance of ReusableThread */
 public ReusableThread(Runnable proc) {
  mProc = proc;
  mListener = null;
  mThread = new Thread(this);
  mState = STATE_READY;
 }
  
 public byte getState() {return mState;}
  
 public void setStateListener(ThreadStateListener listener) {
  mListener = listener;
 }
  
 /**可以在处于等待状态时调用该方法重设用户过程*/
 public synchronized boolean setProcedure(Runnable proc) {
  if (mState == STATE_READY) {
   mProc = proc;
   return true;
  }
  else
   return false;
 }
  
 /**开始执行用户过程*/
 public synchronized boolean start() {
  if (mState == STATE_READY) {
   mState = STATE_STARTED;
   if (!mThread.isAlive()) mThread.start();
   notify(); //唤醒因用户过程执行结束而进入等待中的主线程
   return true;
  }
  else
   return false;
 }
  
 /**结束整个线程,销毁主线程对象。之后将不可再次启动*/
 public synchronized void destroy() {
  mState = STATE_DESTROYED;
  notify();
  mThread = null;
 }
  
 public void run() {
  while (true) {
   synchronized (this) {
    try {
     while (mState != STATE_STARTED) {
      if (mState == STATE_DESTROYED) return;
      wait();
     }
    } catch(Exception e) {e.printStackTrace();}
   }
    
   if (mProc != null) mProc.run();
   if (mListener != null) mListener.onRunOver(this); //当用户过程结束后,执行监听者的onRunOver方法
    
   synchronized (this) {
    if (mState == STATE_DESTROYED) return;
    mState = STATE_READY;
   }
  }
 }
  
}
ログイン後にコピー

コードは理解しやすいですよね。ただし、「ステータス リスナー」インターフェイスが存在する理由を説明してください。追加の処理を実行できるように、ユーザー プロセスの終了後にタイムリーな通知を取得したい場合があります。この場合、ステータス リスナーの onRunOver メソッドが役立ちます。直感的な例としては、以下で説明する「スレッド プール」クラスでは、ユーザー プロセスの実行後に再起動可能なスレッドを自動的にプールに戻す必要があります。このとき、プールに戻るアクションを onRunOver メソッドに配置できます。 、そのパラメータは再起動可能なスレッド オブジェクトであるため、パラメータで示されたオブジェクトはスレッド プールにリサイクルできます。

スレッド プール クラスに関しては、実際には前述のオブジェクト プール クラスのサブクラスであり、その中のすべてのオブジェクトは ReusableThread クラスに属します。さらに、ReusableThread.ThreadStateListener インターフェイスを実装しているので、ユーザー プロセスの終了時に通知を受け、スレッドをリサイクルする作業を実行できます。

public class ThreadPool extends ObjectPool implements ReusableThread.ThreadStateListener {
 public static final int DefaultNumThreads = 16; //默认池容量
  
 public ReusableThread getThread() {
  return (ReusableThread)fetch();
 }
  
 public void onRunOver(ReusableThread thread) {
  recycle(thread); //当用户过程结束时,回收线程
 }
  
 private void init(int size) {
  ReusableThread thread;
  //初始化线程池内容
  for (int i=0; i<size; i++) {
   thread = new ReusableThread(null);
   thread.setStateListener(this);
   setElementAt(thread, i);
  }
 }
  
 public ThreadPool(int size) {
  super(size);
  init(size);
 }
  
 public ThreadPool() {
  super(DefaultNumThreads);
  init(DefaultNumThreads);
 }
  
}
ログイン後にコピー

もちろん、追加する必要がある関数がいくつかあります。通常のスレッドよりスレッドが 1 つだけ多いため、再起動可能な「拡張」スレッド クラスの場合、スレッドの sleep() など、元の Thread クラスの関数も使用できる必要があります。ただし、これらは比較的単純なので、ここでは省略します。

以下のテストプログラムを書きます。スレッド プール クラスを使用する代わりに、オブジェクト プール内のオブジェクトが属するクラス CharEmitter で Runnable インターフェイスとスレッドを実装し、オブジェクト プール クラスと再起動可能なスレッド クラスの結合テストを実行する予定です。ステータス リスナー インターフェイス。スレッド プール オブジェクトには含まれないが、独立して使用される再起動可能なスレッド メンバー オブジェクトが含まれます。このスレッド (CharEmitter クラスで定義) のユーザー プロセスが終了すると、onRunOver メソッドは、この CharEmitter オブジェクトをプールにリサイクルするアクションを実行します。これは、オブジェクト プールとの違いは onRunOver でリサイクル アクションを実行することであるため、スレッド プール クラスを間接的にテストする役割も果たします。

わかりやすくするために、コードに直接アクセスすることをお勧めします:

TestThreadPool.java :

/**字符放射器*/
class CharEmitter implements Runnable, ReusableThread.ThreadStateListener {
 char c; //被发射的字符
 boolean[] isEmitting; //标示某字符是否正被发射(直接以字符对应的ASCII码作下标索引)
 
 ReusableThread thread; //可重启线程对象
 
 ObjectPool myHomePool; //为知道应把自己回收到哪里,需要保存一个到自己所在对象池的引用
 
 CharEmitter(ObjectPool container, boolean[] isCharEmitting) {
  isEmitting=isCharEmitting;
  myHomePool=container;
  thread=new ReusableThread(this); //新建可重启线程对象,设其用户过程为CharEmitter类自己定义的
 }
 
 /**开始“发射”字符*/
 public void emit(char ch) {
  //字符被要求只能是&#39;0&#39;到&#39;9&#39;之间的数字字符
  if (ch>=&#39;0&#39; && ch<=&#39;9&#39;) {
   c=ch;
  }
  else c=&#39; &#39;;
  
  thread.start(); //启动线程
 }
 
 public void run() {
  if (c==&#39; &#39;) return; //若不是数字字符直接结束
  //为便于观察,不同数字之前的空格数目不同,以便将其排在不同列上
  int spaceLen=c-&#39;0&#39;;
  StringBuffer s=new StringBuffer(spaceLen+1);
  for (int i=0; i<spaceLen; i++) s.append(&#39; &#39;);
  s.append(c);
  
  while (isEmitting[c]) {
    System.out.println(s); //不断地向屏幕写字符
  }
 }
 
/**实现线程状态监听者接口中的方法*/
 public void onRunOver(ReusableThread t) {
  myHomePool.recycle(this); //回收自身入池
 }
}
 
 
 
public class TestThreadPool {
 
public static void main(String[] args) {
 // TODO Auto-generated method stub
 //标示字符是否正被发射的标志变量数组
 boolean[] isEmitting=new boolean[256];
 for (int i=0; i<256; i++) isEmitting[i]=false;
  
 ObjectPool emitters=new ObjectPool(10); //新建对象池,容量为10
 for (int i=0; i<10; i++) {
 //用CharEmitter对象填满池子
 emitters.setElementAt(new CharEmitter(emitters, isEmitting), i);
 }
  
 byte[] c=new byte[1];
 CharEmitter emitter;
  
 while(true) {
 try {
 System.in.read(c); //从键盘读入一个字符,以回车键表示输入结束
 } catch(Exception e) {e.printStackTrace();}
  
 if (isEmitting[c[0]]) {
 isEmitting[c[0]]=false; //若字符正被发射,则结束其发射
 }
 else {
 isEmitting[c[0]]=true;
 emitter=(CharEmitter)emitters.fetch(); //向池中索取一个CharEmitter对象
 emitter.emit((char)c[0]); //发射用户输入的字符
 }
 }
}
 
}
ログイン後にコピー

実行後、キーボードから 0 から 9 までの任意の数字を入力して Enter キーを押すと、画面がスクロールし続けます。番号を表示するには、同じ番号を再度入力すると、番号は表示されなくなります。複数の数値が同時に送信されると、異なる数値の表示が交互に表示されることが明らかにわかります。これはまさに、スレッド間での仮想マシンのスケジューリングの結果です。実行結果は、設計したクラス関数が完全に正しいことを示しています。

後で説明する J2ME の Bluetooth 通信の補助クラスでは、スレッド プールと再起動可能なスレッドがかけがえのない役割を果たすことがわかります。

上記の Java 再起動可能スレッドとスレッド プール クラスの設計 (詳細な説明) は、編集者によって共有されたすべての内容です。参考にしていただければ幸いです。また、PHP 中国語 Web サイトをサポートしていただければ幸いです。

Java の再起動可能なスレッドとスレッド プール クラスの設計に関する関連記事 (詳細な説明) については、PHP 中国語 Web サイトに注目してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート