首頁  >  文章  >  Java  >  java 可重啟線程及線程池類別的設計(詳解)

java 可重啟線程及線程池類別的設計(詳解)

高洛峰
高洛峰原創
2017-01-23 16:18:051431瀏覽

了解JAVA多執行緒程式設計的人都知道,要產生一個執行緒有兩種方法,一是類別直接繼承Thread類別並實作其run()方法;二是類別實作Runnable介面並實作其run()方法,然後新建一個以此類別為建構方法參數的Thread,類似於下列形式: Thread t=new Thread(myRunnable)。而最終使執行緒啟動都是執行Thread類別的start()方法。

在JAVA中,一個執行緒一旦運行完畢,即執行完其run()方法,就不可以重新啟動了。此時這個線程對像也便成了無用對象,等待垃圾回收器的回收。下次想再啟動這個線程時,就必須重新new出一個線程物件再start之。頻繁地創建和銷毀物件不僅影響運行效率,還可能因無用線程物件來不及被回收而產生大量的垃圾內存,在儲存空間和處理速度都相對受限的移動平台上這種影響尤為顯著。那麼,能否重新設計一種線程類,使其能夠被反覆啟動而無需頻繁地創建和銷毀物件呢?

當然可以。下面我就介紹一下這個「可重啟線程」類別的設計。

首先必須明確,如果仍是把想要線程去做的任務直接放在線程的run()方法中,是無論如何無法達成目的的,因為就像上面已經說的,JAVA的線程類別一旦執行完run()方法就無法再啟動了。所以唯一可行的辦法是,把使用者程式要做的run()方法(不妨稱作「使用者流程」)套在執行緒實際的run()方法內部的while循環體內,當使用者過程執行完後使執行緒wait 。當呼叫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);
 }
  
}

當然,還有一些可能需要添加的功能,因為既然只是比普通線程多了一個可重啟的「增強」型線程類,那麼原來Thread類別具有的功能也應該具有,例如線程的sleep()。不過那些比較簡單,這裡就略去了。

下面編寫測試程式。我準備這樣進行:並不用到線程池類,而是對對像池類和可重啟線程類進行聯合測試,該對像池中的對象所屬的類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之間的任意數字並按回車,之後會不斷地在屏幕上滾動顯示該數字;再次輸入相同的數字則不再顯示該數字。當同時存在多個數字被發射時,可以明顯看出不同數字的顯示是交錯進行的,這正是由於虛擬機器在各線程間調度的結果。運行結果表明,我們設計的類別功能完全正確。

在以後要說的J2ME中藍牙通訊的輔助類別中,將會看到,線程池與可重啟線程起到了不可替代的作用。

以上這篇java 可重啟線程及線程池類的設計(詳解)就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持PHP中文網。

更多java 可重啟線程及線程池類的設計(詳解)相關文章請關注PHP中文網!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn