java rebootable thread and thread pool class design of details

  • 2020-06-01 09:52:29
  • OfStack

Anyone who knows JAVA multithreaded programming knows that there are two ways to generate a thread. The first is that the class directly inherits the Thread class and implements its run() method. 2 is the class that implements the Runnable interface and implements its run() method, and then creates a new Thread that takes this class as a constructor parameter, similar to the form Thread t=new Thread(myRunnable). The start() method of Thread class is executed in the end.

In JAVA, once a thread has run its run() method, it cannot be restarted. At this point, the thread object becomes a garbage collector, waiting for collection. The next time you want to start the thread again, you must re-new a thread object and start a thread object. Frequent creation and destruction of objects not only affects operational efficiency, but can also result in a large amount of garbage memory due to unwanted thread objects not being collected in time, especially on mobile platforms where storage space and processing speed are relatively limited. So, can you redesign a thread class so that it can be started over and over again without the need to constantly create and destroy objects?

Sure. Let me introduce you to the design of this "rebootable thread" class.

First of all, it must be clear that putting the task you want the thread to do directly in the thread's run() method is not going to do the trick anyway, because, as already mentioned, JAVA's thread class 1 will not be able to start once the run() method has been executed. So the only way to do this is to put the run() method that the user program is going to do (call it the "user process") inside the while loop inside the actual run() method of the thread, and make the thread wait when the user process is finished. When you call the restart method to restart a thread, you are actually waking up the waiting thread to start the next while loop. With the general idea in place, the following code is easy to understand:


public class ReusableThread implements Runnable {
 // Thread status listener interface 
 public interface ThreadStateListener {
  public abstract void onRunOver(ReusableThread thread);// The method that is called when the user procedure is completed 
 }
 
 public static final byte STATE_READY=0; // The thread is ready to begin the user process 
 public static final byte STATE_STARTED=1; // The user process has started 
 public static final byte STATE_DESTROYED=2; // Thread final destruction 
 
 byte mState; // Indicates the current state of the rebootable thread 
 
 Thread mThread; // The actual main thread object 
 Runnable mProc; // User process run() Method definition in mProc In the 
 ThreadStateListener mListener; // Status listener, can be 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;
 }
 
 /** This method can be called while in the wait state to reset the user procedure */
 public synchronized boolean setProcedure(Runnable proc) {
  if (mState == STATE_READY) {
   mProc = proc;
   return true;
  }
  else
   return false;
 }
 
 /** Start executing the user procedure */
 public synchronized boolean start() {
  if (mState == STATE_READY) {
   mState = STATE_STARTED;
   if (!mThread.isAlive()) mThread.start();
   notify(); // Wakes up the main thread that is waiting because the user process execution has finished 
   return true;
  }
  else
   return false;
 }
 
 /** Terminates the entire thread and destroys the main thread object. It will not start again after that */
 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); // When the user process is over, execute the listener's onRunOver methods 
   
   synchronized (this) {
    if (mState == STATE_DESTROYED) return;
    mState = STATE_READY;
   }
  }
 }
 
}

The code is easy to understand, right? But explain 1 why there is a "status listener" interface. Sometimes we might want to get a prompt notification at the end of the user process for additional processing, where the status listener's onRunOver method becomes useful. An intuitive example is that in the following mention "thread pool" in the class, one can restart thread execution after one user process shall be automatically back to income pool, then can be put back to the action of income pool onRunOver method, and its parameters is the resumption of thread object, so you can put the object is indicated by the parameter collection into the thread pool.

As for the thread pool class, it is actually a subclass of the previously mentioned object pool class, in which the objects are all of the ReusableThread class. In addition, it implements the ReusableThread.ThreadStateListener interface so that users can be notified at the end of the process to perform the work of the recycle thread:


public class ThreadPool extends ObjectPool implements ReusableThread.ThreadStateListener {
 public static final int DefaultNumThreads = 16; // Default pool capacity 
 
 public ReusableThread getThread() {
  return (ReusableThread)fetch();
 }
 
 public void onRunOver(ReusableThread thread) {
  recycle(thread); // When the user process ends, the thread is reclaimed 
 }
 
 private void init(int size) {
  ReusableThread thread;
  // Initializes the thread pool contents 
  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);
 }
 
}

Of course, there are a few more features that might need to be added, because since there is just one more rebootable "enhanced" thread class than normal threads, the original Thread class should have the same functionality, such as sleep() for threads. But those are easy, so I'm going to skip them.

Now write the test program. I am ready to do so: do not use thread pool class, but the object classes and class can restart the thread pool for joint test, the object in the pool CharEmitter objects belonging to the class implements the Runnable interface and thread state listener interface, and contains a member can restart the thread object, it is not contained in any thread pool object, but used independently. When the user procedure for this thread (defined in the CharEmitter class) is completed, the onRunOver method performs the action of recycling the CharEmitter object into the pool. This also ACTS as an indirect test of the thread pool class, since it is no more than an object pool to perform the recycle action in onRunOver.

Or directly on the code to make it clear:

TestThreadPool.java :


/** Character radiator */
class CharEmitter implements Runnable, ReusableThread.ThreadStateListener {
 char c; // The character to be emitted 
 boolean[] isEmitting; // Indicates whether a character is being emitted ASCII Code for subscript index) 

 ReusableThread thread; // Rebootable thread object 

 ObjectPool myHomePool; // Need to save in order to know where to recycle yourself 1 A reference to its own pool of objects 

 CharEmitter(ObjectPool container, boolean[] isCharEmitting) {
  isEmitting=isCharEmitting;
  myHomePool=container;
  thread=new ReusableThread(this); // Create a new rebootable thread object and set its user procedure as CharEmitter Class itself 
 }

 /** Start the "emit" character */
 public void emit(char ch) {
  // Characters are required to be '0' to '9' Between the numeric characters 
  if (ch>='0' && ch<='9') {
   c=ch;
  }
  else c=' ';
 
  thread.start(); // Starting a thread 
 }

 public void run() {
  if (c==' ') return; // If not a numeric character ends directly 
  // For the sake of observation, the number of Spaces before each number is different so that it can be arranged in different columns 
  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); // Constantly writing characters to the screen 
  }
 }

/** Implements methods in the thread status listener interface */
 public void onRunOver(ReusableThread t) {
  myHomePool.recycle(this); // Recycle yourself into the pool 
 }
}



public class TestThreadPool {

public static void main(String[] args) {
 // TODO Auto-generated method stub
 // Array of flag variables that indicate whether a character is being emitted 
 boolean[] isEmitting=new boolean[256];
 for (int i=0; i<256; i++) isEmitting[i]=false;
 
 ObjectPool emitters=new ObjectPool(10); // Create a new object pool with a capacity of 10
 for (int i=0; i<10; i++) {
 // with CharEmitter Objects fill the pool 
 emitters.setElementAt(new CharEmitter(emitters, isEmitting), i);
 }
 
 byte[] c=new byte[1];
 CharEmitter emitter;
 
 while(true) {
 try {
 System.in.read(c); // Read in from the keyboard 1 The end of the input is indicated by the enter key 
 } catch(Exception e) {e.printStackTrace();}
 
 if (isEmitting[c[0]]) {
 isEmitting[c[0]]=false; // If the character is being emitted, the transmission ends 
 }
 else {
 isEmitting[c[0]]=true;
 emitter=(CharEmitter)emitters.fetch(); // Take it from the pool 1 a CharEmitter object 
 emitter.emit((char)c[0]); // Emits a character entered by the user 
 }
 }
}

}

After execution, type in any number between 0 and 9 on the keyboard and press enter, which is then repeatedly scrolled across the screen. Enter the same number again and it will no longer be displayed. When multiple Numbers are simultaneously emitted, it is obvious that the display of different Numbers is staggered, which is precisely the result of virtual machine scheduling between threads. The result shows that the function of the class we designed is completely correct.

As you will see later in the J2ME bluetooth communication helper class, thread pools play an irreplaceable role with rebootable threads.


Related articles: