java 可重啟線程及線程池類的設計
https://blog.csdn.net/chenqiu1024/article/details/2829827
了解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) { //字符被要求隻能是'0'到'9'之間的數字字符 if (ch>='0' && ch<='9') { c=ch; } else c=' '; thread.start(); //啟動線程 } public void run() { if (c==' ') return; //若不是數字字符直接結束 //為便於觀察,不同數字之前的空格數目不同,以便將其排在不同列上 int spaceLen=c-'0'; StringBuffer s=new StringBuffer(spaceLen+1); for (int i=0; i<spaceLen; i++) s.append(' '); 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中藍牙通訊的輔助類中,將會看到,線程池與可重啟線程起到了不可替代的作用。
最後更新:2017-04-03 18:52:11