ThreadPoolUtil thread pool usage

Keywords: Java IE

First, why use multithreading:

Today's computers are multi-core, that is, they can handle multiple tasks at the same time and start multi-process, so using multi-threading can effectively utilize cpu resources and improve work efficiency.

2. Thread life cycle:

Third, the difference between thread pool and multithreading:

Multi-threading: Multiple threads can be created through new Thread, but each time the performance of new objects is poor, threads lack unified management, new threads may be unrestricted, competing with each other, and may occupy too much system resources leading to crash or oom; nor can special functions be performed, such as timing, periodic execution, threads in the middle. Interruption and other operations.

Thread pool: It can reuse threads, reduce the overhead of creating and destroying objects, and achieve better performance. At the same time, it can control the maximum number of concurrent threads of effective threads, avoid thread blocking, etc. to achieve complex tasks.

4. Commonly used multithreading:

In Executors of java.util.concurrent, there are commonly used methods to create thread pools. Four common methods are summarized as follows:

New Cached ThreadPool: Create a cacheable thread pool that can flexibly reclaim idle threads if the length of the thread pool exceeds the processing requirement, and create new threads if it is not.

Source code as shown below:

Parameters:

/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

Parametric description of the construction method:
corePoolSize: Number of core threads in the thread pool. This parameter is not written to death at the time of initialization. After the thread pool object is constructed, it can be dynamically modified by some methods provided by it, ThreadPoolExecutor.setCorePoolSize.

Maximum PoolSize: The maximum number of threads in the thread pool, creating new threads only when the task queue is full. This parameter is not written to death at the time of initialization. After the thread pool object is constructed, it can be dynamically modified by some methods provided by it, ThreadPoolExecutor.setMaximumPoolSize.

keepAliveTime: The activity time of the thread maintained by the thread pool. If the time limit is exceeded and the thread is idle, the thread will be reclaimed. No longer maintained by thread pools. Here are some official explanations:
If the current number of thread pools is larger than the number of core thread pools, and these threads are idle and keep AliveTime over the set survival time, these additional threads will be terminated, which can reduce thread resource consumption. setKeepAliveTime can be set manually by setting the maximum expiration time to prevent idle threads from being destroyed. By default, this policy can only be applied to threads larger than the number of core threads, but it can also be applied to the number of core threads by setting ThreadPoolExecutor.allowCoreThreadTimeOut(boolean).

Unit: Corresponds to the above keepAliveTime, which represents the time unit of activity time. Such as milliseconds, seconds, minutes.

WorQueue: Initializes a set of tasks, but they are not executed immediately. You need to manually call prestartCoreThread or prestartAllCoreThreads to pre-start threads to perform these initialized tasks. Otherwise, it will only execute when you submit the first task, and it may be single-threaded (depending on how many tasks you submit)..

ThreadFactory: Imports a thread factory. Threads can be created through this thread factory. It creates all threads through the same ThreadGroup and the same NORM_PRIORITY and non-daemon states. By providing different ThreadFactory, you can master the modification of thread name, thread group, priority, daemon status and so on. If the thread pool calls the thread factory to create a thread fails, a null is returned. And the executor will continue, but it may not perform any tasks.
If we rewrite the wrapped thread factory ourselves, another advantage is that all threads created by it can be maintained through the thread factory instance.
 

Developers use hook methods such as ThreadPoolExecutor.beforeExecute and ThreadPoolExecutor.afterExecute. These methods can be executed before and after the task is executed, or when the thread terminates. Scenario: Reinitialize ThreadLocal. Collect statistics and add log records. The source code is as follows:

protected void beforeExecute(Thread t, Runnable r) { }
/*  <pre> {@code
 * class PausableThreadPoolExecutor extends ThreadPoolExecutor {
 *   private boolean isPaused;
 *   private ReentrantLock pauseLock = new ReentrantLock();
 *   private Condition unpaused = pauseLock.newCondition();
 *
 *   public PausableThreadPoolExecutor(...) { super(...); }
 *
 *   protected void beforeExecute(Thread t, Runnable r) {
 *     super.beforeExecute(t, r);
 *     pauseLock.lock();
 *     try {
 *       while (isPaused) unpaused.await();
 *     } catch (InterruptedException ie) {
 *       t.interrupt();
 *     } finally {
 *       pauseLock.unlock();
 *     }
 *   }
 *
 *   public void pause() {
 *     pauseLock.lock();
 *     try {
 *       isPaused = true;
 *     } finally {
 *       pauseLock.unlock();
 *     }
 *   }
 *
 *   public void resume() {
 *     pauseLock.lock();
 *     try {
 *       isPaused = false;
 *       unpaused.signalAll();
 *     } finally {
 *       pauseLock.unlock();
 *     }
 *   }
 * }}</pre>
 */

 protected void afterExecute(Runnable r, Throwable t) { }
    /* <pre> {@code
     * class ExtendedExecutor extends ThreadPoolExecutor {
     *   // ...
     *   protected void afterExecute(Runnable r, Throwable t) {
     *     super.afterExecute(r, t);
     *     if (t == null && r instanceof Future<?>) {
     *       try {
     *         Object result = ((Future<?>) r).get();
     *       } catch (CancellationException ce) {
     *           t = ce;
     *       } catch (ExecutionException ee) {
     *           t = ee.getCause();
     *       } catch (InterruptedException ie) {
     *           Thread.currentThread().interrupt(); // ignore/reset
     *       }
     *     }
     *     if (t != null)
     *       System.out.println(t);
     *   }
     * }}</pre>
     */
 protected void terminated() { }

Tool classes are as follows:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolUtil
{
  private static volatile ThreadPoolUtil instance;
  private static ExecutorService threadPool;
  
  public static ThreadPoolUtil getInstance()
  {
    if (instance == null) {
      synchronized (ThreadPoolUtil.class)
      {
        instance = new ThreadPoolUtil();
        threadPool = Executors.newCachedThreadPool();
      }
    }
    return instance;
  }
  
  public void excute(Runnable runnable)
  {
    threadPool.execute(runnable);
  }
  
  public void shutdown()
  {
    threadPool.shutdown();
  }
  
  public boolean isActive()
  {
    if (threadPool.isTerminated()) {
      return false;
    }
    return true;
  }

    /*//Method calls
     ThreadPoolUtil.getInstance().excute(new Runnable()
        {
          public void run()
          {
            //Call to execute specific business
          }
       });
   */
}

New Fixed ThreadPool: Create a fixed-length thread pool to control the maximum number of concurrent threads, and the threads that exceed it will wait in the queue.
New Scheduled ThreadPool: Create a fixed-length thread pool that supports both regular and periodic task execution.
New Single ThreadExecutor: Create a single threaded thread pool that only uses a single worker thread to perform tasks, ensuring that all tasks are executed in the specified order (FIFO, LIFO, priority).
The other methods are similar.

5. Thread pool execution process

We submit it or execute to the thread pool, which calls the inner class Worker, which implements Runnable and encapsulates threads. They pass this, or Worker, into Thread's construction parameters when creating threads through the thread factory, and maintain a set of Workers from the code. With Worker.thread.start(), the thread wrapped by Worker runs the run implementation of Worker itself. Finally, in the run implementation, tasks are continuously retrieved from the workQueue to execute. Because workQueue is a blocking queue, if there are no tasks in the queue, the thread will be hung up and awakened (tasks come over). The worker class encapsulates Thread and task Runnable, as well as completedTasks. You can notice that this is passed in when creating a Thread, so if I call Worker.thread.start(), that thread will execute the run method in Worker. Completed Tasks are used to record how many tasks (not the entire thread pool) the thread has completed.
Note that the Worker inherits the AQS synchronization infrastructure, which mainly implements mutex, but this mutex is somewhat different from ReentrantLock, which does not allow threads to reenter the acquisition lock. Here's why lock functionality and non-reentry are implemented:
1.lock method is mainly used to indicate that the current thread is executing a task, while private interruptIdleWorkers method needs to use tryLock to determine whether the current thread is executing a task. If the non-executing task state indicates that it may be acquiring a task, then the thread is idle and can be interrupted.
2. Look at Answer 1 and you can see that this can also be achieved through ReentrantLock, but if we are committing a task to the thread pool (implementing a Runnable), if the
The task calls interruptIdleWorkers and interrupts the thread currently accessible (representing idle) locks. If we don't use non-reentrant locks, the task thread will be interrupted, causing some strange problems.

The source code is as follows:

 private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
            return getState() != 0;
        }
        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
              setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }
        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

 

Posted by phparmy on Fri, 06 Sep 2019 23:45:29 -0700