ThreadPool Executor Source Analysis Learning for Java Concurrent Thread Pool

Keywords: Java less Programming JDK

Thread pool learning

All the following contents and source code analysis are based on JDK 1.8, please know.

My blog is really out of order, which may be related to my learning style. I think it's not good for me, but I can't convince myself to change, so I can only think about what to learn.

Pooling technology is really a very powerful technology in my opinion, because it achieves the maximum utilization of resources within the limited resources, which reminds me of a course, operational research, which often did this kind of similar problem in operational research at that time.

Let's get down to business. I'll do a thread pool learning next, and I'll also record and share it with you.

Thread pool contains knowledge points related to AQS synchronizer. If you feel a little weak about AQS synchronizer knowledge points, you can see my last article.

Advantages of Thread Pool

Since we are talking about thread pools, and most Daniels will also suggest that we use pooling technology to manage some resources, thread pools certainly have its advantages, otherwise how can they be so famous and used by everyone?

Let's see what advantages it has.

  • Resource Controllability: Using thread pools avoids memory consumption by creating a large number of threads

  • Improving response speed: Thread pool creation is actually time-consuming and performance-intensive. Thread pool creation can run with tasks and improve response speed.

  • Easy to manage: One of the most prominent features of pooling technology is that it can help us manage the resources in the pool. It is allocated and managed by thread pool.

Creation of thread pool

We need to use thread pool to distribute and manage our threads. First, we need to create a thread pool, or there are many bulls who have helped us write a lot of code. Executors'factory method provides us with a variety of ways to create different thread pools. Because this class is only a factory for creating objects and does not involve many specific implementations, I will not go into too much detail.

The old rule is to code directly.

public static ExecutorService newFixedThreadPool(int nThreads) {
      return new ThreadPoolExecutor(nThreads, nThreads,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>());
  }

Here's an example of a method to illustrate later. We can see that Executors is just a factory, and methods are just to instantiate different objects. In fact, the key class to instantiate is ThreadPoolExecutor. Now let's briefly explain each parameter in the ThreadPoolExecutor constructor.

  • CorePoolSize (Core Thread Pool Size): When a task is submitted to the thread pool, the thread pool creates a thread to execute the task. Even if other idle basic threads can execute a new task, the thread will be created. When the number of tasks is larger than the number of core threads, it will not be created again. One thing to note here is that when the thread pool was created, it did not create any threads, but waited for tasks to create threads, unless the prestartAllCoreThreads() or prestartCoreThread() method was invoked, so that corePoolSize threads or a thread could be pre-created.

  • Maximum PoolSize: The maximum number of threads allowed to be created in the thread pool. If the queue is full and the number of threads created is less than the maximum number of threads, the thread pool will create new threads to perform tasks. It is worth noting that this parameter is meaningless if unbounded queues are used.

  • Keep AliveTime (thread activity retention time): This parameter will only work if the number of threads is larger than corePoolSize by default, and will terminate when the idle time of the thread reaches keep AliveTime until the number of threads is less than corePoolSize. However, if the allowCoreThreadTimeOut method is called, it also works when the number of threads is less than corePoolSize.

  • Unit (keel AliveTime time unit): There are seven kinds of keel AliveTime units, which are not listed here.

  • WorkQueue (blocking queue): Blocking queue is used to store tasks waiting to be executed. This parameter is also very important. Here are a few blocking queues.

    • Array BlockingQueue: This is a bounded blocking queue based on array structure, which sorts elements according to FIFO principles.

    • LinkedBlockingQueue: A linked list-based blocking queue with FIFO-ordered elements and generally higher throughput than Array BlockingQueue. The static factory method Executors.newFixedThreadPool() uses this queue.

    • SynchronousQueue: A blocking queue that does not store elements. Each insert operation must wait until another thread calls the removal operation, otherwise the insert operation will remain blocked. Throughput is usually higher than LinkedBlockingQueue, which is used by the static factory method Executors.newCachedThreadPool().

    • Priority Blocking Queue: A non-blocking queue with priority.

  • Handler (saturation strategy); when the thread pool and queue are full, indicating that the thread pool is already saturated, then a strategy must be adopted to deal with the new tasks still submitted. This saturation policy is AbortPolicy by default, indicating that an exception is thrown when a new task cannot be handled. There are four saturation strategies to provide, of course, we can also choose our own saturation strategy.

    • AbortPolicy: Discard and throw the RejectedExecutionException exception directly

    • CallerRunsPolicy: Run tasks only with the thread in which the caller lives.

    • Discard Oldest Policy: Discard the latest task in the queue and execute the current task.

    • DiscardPolicy: Discards tasks and does not throw exceptions.

The execution process of thread pool is introduced with the figure in the reference. Specifically, we explain it through code.

On the above, we briefly explained the factory method in Executors factory class, and described some parameters of creating thread pools and their functions. Of course, the above explanation is not very in-depth, because it takes time to understand what you want to understand, and the blogger himself still does not fully understand, but the blogger's learning method is. After learning a general outline, it may be better to look back at the previous knowledge points, so let's go on to the following.

ThreadPool Executor Source Code Analysis

We found that the factory method of Executors mainly returns the ThreadPoolExecutor object, but the other one is not mentioned here for the moment, that is to say, to learn thread pool, the key is to learn to analyze the source code in the ThreadPoolExecutor object, and then we will analyze the key code in the ThreadPoolExecutor.

AtomicInteger ctl

ctl is the main control state and a compound variable, which includes two concepts.

  • workerCount: Represents the number of valid threads

  • runState: The running state of threads in the thread pool

Let's analyze some of the source code related to ctl and go directly to the code.

     private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

     //The number of digits used to represent the number of thread pools is obviously 29, Integer.SIZE=32
     private static final int COUNT_BITS = Integer.SIZE - 3;
     //Maximum number of thread pools, 2 ^ 29 - 1
     private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

     // runState is stored in the high-order bits
     //We can see that there are five runState states, proving that at least three bits are needed to represent the runState state.
     //So the top three is runState.
     private static final int RUNNING    = -1 << COUNT_BITS;
     private static final int SHUTDOWN   =  0 << COUNT_BITS;
     private static final int STOP       =  1 << COUNT_BITS;
     private static final int TIDYING    =  2 << COUNT_BITS;
     private static final int TERMINATED =  3 << COUNT_BITS;

     // Packing and unpacking ctl
     private static int runStateOf(int c)     { return c & ~CAPACITY; }
     private static int workerCountOf(int c)  { return c & CAPACITY; }
     private static int ctlOf(int rs, int wc) { return rs | wc; }

     //Blocking queues for storing thread tasks
     private final BlockingQueue<Runnable> workQueue;

     //Reentrant lock
     private final ReentrantLock mainLock = new ReentrantLock();

     //The collection of threads in the thread pool can only be accessed if it has a mainLock lock
     private final HashSet<Worker> workers = new HashSet<Worker>();

     //Waiting conditions support termination
     private final Condition termination = mainLock.newCondition();

     //Create thread factories for new threads
     private volatile ThreadFactory threadFactory;

     //Saturation strategy
     private volatile RejectedExecutionHandler handler;
  1. CAPACITY

    Let's talk about the calculation of the maximum number of thread pools, because it involves operations such as source code and displacement. I feel that most people still don't know much about this, because I didn't know much when I first saw it.

private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

From the code, we can see that it is necessary to move 1 to the left 29 bits, and then subtract 1. How does that move 1 to the left 29 bits calculate?

     1 << COUNT_BITS
      ​
      The 32-bit binary of 1 is
      00000000 00000000 00000000 00000001
      ​
      If you move 29 places to the left, that's it.
      00100000 00000000 00000000 00000000
      ​
      Subtract one more operation
      000 11111 11111111 11111111 11111111
      ​
      That is to say, the maximum number of thread pools is
      000 11111 11111111 11111111 11111111

2.runState

Positive primitive codes, inverse codes and complements are all the same.
At the bottom of the computer, it is represented by complements.

private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING    = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
  • RUNNING

    New tasks are acceptable and tasks that are already blocked are processed
    If all three digits are 1, it's RUNNING status.

-1 << COUNT_BITS

Here is - 1 shifting 29 bits to the left, slightly different, - 1, we need to figure out the complement by ourselves.
          ​
The original code of -1
10000000 00000000 00000000 00000001
          ​
- The inverse code of 1, the inverse code of negative number is the inverse of all the original codes except the symbol bits.
11111111 11111111 11111111 11111110
          ​
- The complement of 1, the complement of negative number is the countercode + 1
11111111 11111111 11111111 11111111
          ​
Crucially, move 29 bits to the left, so all three bits are in the RUNNING state.
111 00000 00000000 00000000 00000000
  • SHUTDOWN

    Do not accept new tasks, but handle tasks that are already blocking queues
    High 3 bits are all 0, that is SHUTDOWN state

0 << COUNT_BITS
          ​
0 representation
00000000 00000000 00000000 00000000
          ​
Move 29 bits to the left
00000000 00000000 00000000 00000000
  • STOP

    Do not accept new tasks, do not handle tasks in the blocking queue, and interrupt tasks being processed
    So the top three is 001, which is the STOP state.

1 << COUNT_BITS
          ​
1 representation
00000000 00000000 00000000 00000001
          ​
Move 29 bits to the left
00100000 00000000 00000000 00000000
  • TIDYING

    All tasks are aborted, workerCount is 0, thread state is converted to TIDYING and terminated() hook method is called
    So the top three is 010, which is the TIDYING state.

2 << COUNT_BITS
          ​
32-bit binary of 2
00000000 00000000 00000000 00000010
          ​
Move 29 bits to the left
01000000 00000000 00000000 00000000
  • TERMINATED

    terminated() hook method has been completed
    So the top three is 110, which is the TERMINATED state.

3 << COUNT_BITS
          ​
32-bit binary of 3
00000000 00000000 00000000 00000011
          ​
Move 29 bits to the left
11000000 00000000 00000000 00000000

3. Introduction of some methods

  • runStateOf(int c)

Real-time acquisition of runState

private static int runStateOf(int c)     { return c & ~CAPACITY; }
~CAPACITY
 ~ It means to take the opposite position.
& It means by position and by position.
          ​
And CAPACITY is, high 3 0, low 29 are all 1, so it is.
000 11111 11111111 11111111 11111111
          ​
If the opposite is true,
111 00000 00000000 00000000 00000000
          ​
The incoming c-parameters and the retrieved CAPACITY are bitwise and operated
 1. Low 29 zeros for bit-by-bit and, or 29 zeros
 2. High position 3 1, keeping the high position 3 of c parameter
 By keeping the high bit as it is and the low 29 bit as zero, you get the runState running state of the thread pool.
  • workerCountOf(int c)

Gets the current number of valid threads in the thread pool

private static int workerCountOf(int c)  { return c & CAPACITY; }
The 32-bit binary of CAPACITY is
000 11111 11111111 11111111 11111111
          ​
Bit-by-bit and operation with reference c and CAPACITY
 1. Low 29 bits are all 1, so keep the low 29 bits of c, that is, the number of valid threads.
2. The high three bits are all 0, so the high three bits of c are also 0.
          ​
So what you get is the value of workerCount.
  • ctlOf(int rs, int wc)

    Initialization Method of Atomic Integer Variable ctl

//Consider these lines of code
private static final int RUNNING    = -1 << COUNT_BITS;
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
          ​
private static int ctlOf(int rs, int wc) { return rs | wc; }
RUNNING is
111 00000 00000000 00000000 00000000
          ​
ctlOf is a bit or operation of rs and wc
          ​
Initialize RUNNING and 0 bit by bit or
 The 32-bit binary of 0 is
00000000 00000000 00000000 00000000
          ​
So the initialized ctl is
111 00000 00000000 00000000 00000000

Core Method Source Code Analysis

  1. execute(Runnable command) method
      public void execute(Runnable command) {
          //The task command to be executed is empty, throwing a null pointer exception
          if (command == null)  // 1
              throw new NullPointerException();

          /*
          *The process of execution is actually divided into three steps
          *1,If the running thread is smaller than corePoolSize, a new thread is opened to execute with the Runable object given by the user.
          *  And executing the addWorker method checks runState and workerCount with atomic operations to prevent when returning false
          *  When adding threads that should not be added
          *2, If the task can be successfully added to the queue, we still need to double-check the added threads, possibly before adding the threads.
          *  A check is dead, or the thread pool is closed when it enters the method. So we need to review the status, and there is a need.
          *  If you want to, you need to roll back the column operation when you stop, or open a new thread when there are no threads.
          *3,If the task cannot be listed, we need to try adding a new thread. If the new thread fails, we know that the thread may be closed.
          *  Or if it's saturated, it needs to be rejected.
          *
          */

          //Get the control status of thread pool
          int c = ctl.get();  // 2

          //WorkCount value calculated by workCountOf method is less than corePoolSize
          if (workerCountOf(c) < corePoolSize) {
              //Add tasks to worker collections
      if (addWorker(command, true)) 
                  return;  //Successful return
              //Get the control status of the thread pool again if it fails
              c = ctl.get();
          }

          /*
          *Determine whether the thread pool is in RUNNING state
          *Yes, add Runnable objects to the workQueue queue
          */
          if (isRunning(c) && workQueue.offer(command)) {  // 3

              //Get the state of the thread pool again
              int recheck = ctl.get();

              //Check status again
              //Thread pool is not in RUNNING state, removing tasks from workQueue queue
              if (! isRunning(recheck) && remove(command))
                  //Refusal task
                  reject(command);
              //workerCount equals 0
              else if (workerCountOf(recheck) == 0)  // 4
                  //Add worker
                  addWorker(null, false);
          }
          //If joining the blocking queue fails, try to execute the task with the new threads with the maximum number of threads in the thread pool
      else if (!addWorker(command, false))  // 5 
              //Failure to execute will result in rejection of tasks
              reject(command);
      }

Let's talk about the flow of the code above:

1. First, determine whether the task is empty or not, and then throw an empty pointer exception.
2. Get the thread pool control status if it is not empty, determine that it is less than corePoolSize, and add it to the worker collection for execution.

  • If successful, return
  • If it fails, it then retrieves the thread pool control state, because only if the state changes will it fail, so retrieve it.
    3. Determine whether the thread pool is running or not. If yes, add command to the blocking queue. When joining the queue, the state will be retrieved and detected again.
    Whether the state is not running, if not, removes the command from the blocking queue and rejects the task
    4. If there are no threads in the thread pool, create a new thread to execute the task of obtaining the blocking queue
    5. If none of the above is successful, the thread in the largest thread pool needs to be opened to perform the task, and if it fails, it will be discarded.

Sometimes no amount of text is as clear as a flowchart, so we draw an execute flowchart for you to understand.

2.addWorker(Runnable firstTask, boolean core)

      private boolean addWorker(Runnable firstTask, boolean core) {
          //External loop marker
          retry:
          //Outer Dead Cycle
          for (;;) {
              //Get thread pool control status
              int c = ctl.get();
              //Get runState
              int rs = runStateOf(c);
      ​
              // Check if queue empty only if necessary.

              /**
              *1.If the thread pool runState is at least SHUTDOWN
              *2\. One is false and addWorker fails. Look at false.
              * - runState==SHUTDOWN,That is, the state is greater than SHUTDOWN
              * - firstTask For null, that is, the incoming task is empty, combined with the above runState is SHUTDOWN, but
              *  firstTask Not empty. Represents that the thread pool has been closed and the task is still passing in.
              * - Queue empty, since the task is empty, queue empty, there is no need to add tasks to the thread pool
              */
              if (rs >= SHUTDOWN &&  //runState greater than or equal to SHUTDOWN, initial bit RUNNING
                  ! (rs == SHUTDOWN &&  //runState equals SHUTDOWN
                     firstTask == null &&  //First Task is null
                     ! workQueue.isEmpty()))  //The workQueue queue is not empty
                  return false;
      ​
              //Inner Dead Cycle
              for (;;) {
                  //Get the number of workerCount s in the thread pool
                  int wc = workerCountOf(c);
                  //If workerCount exceeds or is larger than corePoolSize/maximumPoolSize
                  //Return to false
                  if (wc >= CAPACITY ||
                      wc >= (core ? corePoolSize : maximumPoolSize))
                      return false;
                  //Through CAS operation, the number of workerCount + 1, success will jump out of the loop, back to the retry tag
                  if (compareAndIncrementWorkerCount(c))
                      break retry;

                  //CAS operation failed to retrieve the control status of thread pool again
                  c = ctl.get();  // Re-read ctl
                  //If the current runState is not equal to the newly acquired runState, jump out of the inner loop and continue the outer loop
                  if (runStateOf(c) != rs)
                      continue retry;
                  // else CAS failed due to workerCount change; retry inner loop
                  //CAS failed to change workerCount and continued the inner loop
              }
          }
      ​
          //Through the above loop, you can execute until this is workerCount success + 1

          //worker start tag
          boolean workerStarted = false;
          //worker add tag
          boolean workerAdded = false;
          //Initialize the worker to null
          Worker w = null;
          try {
              //Initialize a worker object of the current Runnable object
              w = new Worker(firstTask);
              //Get the thread corresponding to the worker
              final Thread t = w.thread;
              //If the thread is not null
              if (t != null) {
                  //Locks in the Initial Thread Pool
                  final ReentrantLock mainLock = this.mainLock;
                  //Get lock
                  mainLock.lock();
                  try {
                      // Recheck while holding lock.
                      // Back out on ThreadFactory failure or if
                      // shut down before lock acquired.
                      //Check again after getting the lock to get the thread pool runState
                      int rs = runStateOf(ctl.get());
      ​
                      //When runState is less than SHUTDOWN or runState is equal to SHUTDOWN and first Task is null
                      if (rs < SHUTDOWN ||
                          (rs == SHUTDOWN && firstTask == null)) {

                          //Threads are alive
                          if (t.isAlive()) // precheck that t is startable
                              //Threads survive without starting, throwing IllegalThreadStateException exceptions
                              throw new IllegalThreadStateException();

                          //Add the worker object to the workers collection
                          workers.add(w);
                          //Get the size of the workers collection
                          int s = workers.size();
                          //If the size exceeds largestPoolSize
                          if (s > largestPoolSize)
                              //Reset largestPoolSize
                              largestPoolSize = s;
                          //Markup worker has been added
                          workerAdded = true;
                      }
                  } finally {
                      //Release lock
                      mainLock.unlock();
                  }
                  //If worker adds successfully
                  if (workerAdded) {
                      //Startup thread
                      t.start();
                      //Markup worker has been started
                      workerStarted = true;
                  }
              }
          } finally {
              //If the worker does not start successfully
              if (! workerStarted)
                  //Operation of workerCount-1
                  addWorkerFailed(w);
          }
          //Returns the label whether the worker is started or not
          return workerStarted;
      }

Let's also briefly talk about the process of this code. It's really very difficult. The blogger stopped writing many times and wanted to break the keyboard.

1. Obtain the control status of thread pool and make judgment. If it does not conform, it will return false. If it does, it will be the next step.
2. Dead loop, judge whether workerCount is larger than the upper limit or larger than corePoolSize/maximumPoolSize, and if not, operate on workerCount+1.
3. If the above judgment or + 1 operation fails, get the control state of the thread pool again. If the runState is inconsistent with the newly acquired runState, jump out of the inner loop and continue the outer loop, otherwise continue the inner loop.
4. +1 operation is successful. ReentrantLock is used to ensure that a worker instance is added to the workers. If the worker instance is added successfully, the instance is started.

Next, take a look at the flow chart to understand an execution process of the above code.

3.addWorkerFailed(Worker w)

When the addWorker method fails to add a worker and fails to start a task successfully, it calls this method, removes the task from the workers, and the workerCount does the - 1 operation.

      private void addWorkerFailed(Worker w) {
          //Reentrant lock
          final ReentrantLock mainLock = this.mainLock;
          //Get lock
          mainLock.lock();
          try {
              //If the worker is not null
              if (w != null)
                  //workers remove worker
                  workers.remove(w);
              //Through CAS operation, workerCount-1
              decrementWorkerCount();
              tryTerminate();
          } finally {
              //Release lock
              mainLock.unlock();
          }
      }

4.tryTerminate()

When an abnormal successful logic operation is performed on the thread pool, tryTerminate attempts to terminate the thread pool are required.

      final void tryTerminate() {
          //Dead cycle
          for (;;) {
              //Get thread pool control status
              int c = ctl.get();

              /*
              *Thread pool is in RUNNING state
              *Thread pool state minimum greater than TIDYING
              *Thread pool == SHUTDOWN and workQUeue is not empty
              *Direct return, not termination
              */
              if (isRunning(c) ||
                  runStateAtLeast(c, TIDYING) ||
                  (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                  return;

              //If workerCount is not zero
              if (workerCountOf(c) != 0) { // Eligible to terminate
                  interruptIdleWorkers(ONLY_ONE);
                  return;
              }
      ​
              //Get the lock for the thread pool
              final ReentrantLock mainLock = this.mainLock;
              //Get lock
              mainLock.lock();
              try {
                  //Set the thread pool state to TIDYING by CAS operation
                  if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                      try {
                          terminated();
                      } finally {
                          //Set the status of thread pool to TERMINATED
                          ctl.set(ctlOf(TERMINATED, 0));
                          //Send a release signal to a thread waiting on termination condition
                          termination.signalAll();
                      }
                      return;
                  }
              } finally {
                  //Release lock
                  mainLock.unlock();
              }
              // else retry on failed CAS
          }
      }

5.runWorker(Worker w)

The purpose of this method is to perform tasks.

final void runWorker(Worker w) {
      //Get the current thread
      Thread wt = Thread.currentThread();
      //Get the tasks in the worker
      Runnable task = w.firstTask;
      //Assign the task of the worker instance to null
      w.firstTask = null;

      /*
      *unlock Method calls the release method of AQS
      *release Method calls the tryRelease method of the concrete implementation class, Worker
      *That is to say, the AQS state is set to 0, allowing interruption.
      */
      w.unlock(); // allow interrupts
      //Suddenly completed or not
      boolean completedAbruptly = true;
      try {
          //The task of the worker instance is not empty, or the task obtained through getTask is not empty.
          while (task != null || (task = getTask()) != null) {
              //Get lock
              w.lock();
              // If pool is stopping, ensure thread is interrupted;
              // if not, ensure thread is not interrupted.  This
              // requires a recheck in second case to deal with
              // shutdownNow race while clearing interrupt
              /*
              *Get the control state of the thread pool, at least greater than the STOP state
              *If the status is incorrect, check whether the current thread is interrupted and clear the interrupt status, and check again whether the thread pool status is greater than STOP.
              *If the above is satisfied, check whether the object is in an interrupt state and do not clear the interrupt flag.
              */
              if ((runStateAtLeast(ctl.get(), STOP) ||
                   (Thread.interrupted() &&
                    runStateAtLeast(ctl.get(), STOP))) &&
                  !wt.isInterrupted())
                  //Interrupt change object
                  wt.interrupt();
              try {
                  //Pre-execution methods are implemented by subclasses
                  beforeExecute(wt, task);
                  Throwable thrown = null;
                  try {
                      //Perform tasks
                      task.run();
                  } catch (RuntimeException x) {
                      thrown = x; throw x;
                  } catch (Error x) {
                      thrown = x; throw x;
                  } catch (Throwable x) {
                      thrown = x; throw new Error(x);
                  } finally {
                      //The method called after execution is also implemented by subclasses.
                      afterExecute(task, thrown);
                  }
              } finally {//After execution
                  //task is set to null
                  task = null;
                  //Number of completed tasks + 1
                  w.completedTasks++;
                  //Release lock
                  w.unlock();
              }
          }
          completedAbruptly = false;
      } finally {
          //Processing and exiting the current worker
          processWorkerExit(w, completedAbruptly);
      }
  }

Next, we will use words to illustrate the specific logic and process of the task execution method.

  1. First, as soon as the method comes in, w.unlock() is executed in order to change the state of AQS to 0, because only when getState () >= 0 can the thread be interrupted.
  2. To determine whether first Task is empty or not, the task is acquired by getTask() instead of empty and then executed downward.
  3. Determine whether the interruption status is met, and set the interruption marker if it meets the interruption status.
  4. Execute beforeExecute(), task.run(), afterExecute() methods
  5. Any exception will result in the termination of task execution; enter processWorkerExit to exit the task
  6. If it works properly, it goes back to step 2.

Attached is a simple flow chart:

6.getTask()

In the runWorker method above, we can see that when the first Task is empty, it will be used to get tasks to execute. So let's see what the method of getting tasks is like.

      private Runnable getTask() {
          //Task timeout logo acquisition
          boolean timedOut = false; // Did the last poll() time out?
      ​
          //Dead cycle
          for (;;) {
              //Get the control status of thread pool
              int c = ctl.get();
              //Get the runState of the thread pool
              int rs = runStateOf(c);
      ​
              // Check if queue empty only if necessary.
              /*
              *To determine the state of the thread pool, there are two situations
              *1,runState State greater than or equal to SHUTDOWN
              *2,runState Over or equal to STOP or blocking queue empty
              *workerCount-1 will be operated through CAS and null will be returned
              */
              if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                  decrementWorkerCount();
                  return null;
              }
      ​
              //Get workerCount for thread pool
              int wc = workerCountOf(c);
      ​
              // Are workers subject to culling?

              /*
              *allowCoreThreadTimeOut: Whether core Thread timeout is allowed, default false
              *workerCount Is it larger than the core core thread pool?
              */
              boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
      ​
              /*
              *1,wc Greater than maximumPoolSize or timed out
              *2,Queues are not space-time guaranteed to have at least one task
              */
              if ((wc > maximumPoolSize || (timed && timedOut))
                  && (wc > 1 || workQueue.isEmpty())) {
                  /*
                  *Through CAS operation, workerCount-1
                  *Can perform - 1 operation to prove that wc is greater than maximumPoolSize or has timed out
                  */
                  if (compareAndDecrementWorkerCount(c))
                      //- 1 Operate successfully, return null
                      return null;
                  //- 1 Operation failed, continue cycle
                  continue;
              }
      ​
              try {
                  /*
                  *wc Larger than the core thread pool
                  *Execute poll method
                  *Less than the core thread pool
                  *Executing the take method
                  */
                  Runnable r = timed ?
                      workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                  workQueue.take();
                  //Judging that the task is not an empty return task
                  if (r != null)
                      return r;
                  //Acquisition is not available for a period of time. Acquisition timeout
                  timedOut = true;
              } catch (InterruptedException retry) {
                  timedOut = false;
              }
          }
      }

Or explain the above code logic and process in words:

  1. Get the thread pool control status and runState to determine whether the thread pool has been closed or is closing, and if so, the workerCount-1 operation returns null
  2. Get workerCount to determine whether it is larger than the core thread pool
  3. Determine whether workerCount is larger than the maximum number of thread pools or has timed out. Yes, workerCount-1 returns null if it succeeds, and if it fails, it goes back to step 1 and resumes.
  4. Determine whether workerCount is larger than the core thread pool or not, then use poll method to get tasks from the queue, otherwise use take method to get tasks from the queue.
  5. Determine whether the task is empty, return to the acquired task if it is not empty, or go back to step 1 and resume.

Next there is a flow chart:

7.processWorkerExit

Obviously, in the execution of tasks, we will get the task to execute. Since it is the execution of tasks, there will definitely be execution or abnormal interruption of execution. At that time, there will certainly be corresponding operations. As for the specific operation, we still directly see the source code is the most practical.

     private void processWorkerExit(Worker w, boolean completedAbruptly) {
          /*
          *completedAbruptly:In runWorker, the meaning of whether or not it was suddenly completed
          *That is, when an exception occurs during the execution of a task, it will suddenly complete and pass true.
          *
          *If it is completed suddenly, it needs to be operated through CAS, workerCount-1
          *If it's not done suddenly, it doesn't need - 1, because - 1 is already in the getTask method.
          *
          *The following code comment seems to have the opposite meaning of the code
          */
          if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
              decrementWorkerCount();
      ​
          //Generate reentrant locks
          final ReentrantLock mainLock = this.mainLock;
          //Get lock
          mainLock.lock();
          try {
              //Completed TaskCount plus the number of tasks completed in the worker
              completedTaskCount += w.completedTasks;
              //Remove from HashSet < Worker >
              workers.remove(w);
          } finally {
              //Release lock
              mainLock.unlock();
          }
      ​
          //Because the above operation is to release tasks or threads, the state of the thread pool is determined and the thread pool is attempted to terminate.
          tryTerminate();
      ​
          //Get the control status of thread pool
          int c = ctl.get();

          //Judging whether runState is STOP, that is, RUNNING or SHUTDOWN
          //If it is RUNNING or SHUTDOWN, it means that the thread pool has not been successfully terminated.
          if (runStateLessThan(c, STOP)) {
              /*
              *Suddenly completed or not
              *If not, the delegate has no task to complete because the while loop is in getTask
              */
              if (!completedAbruptly) {
                  /*
                  *allowCoreThreadTimeOut:Whether core thread timeout is allowed, default false
                  *min-The default is corePoolSize
                  */
                  int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                  //Allow core thread s to timeout and the queue is not empty
                  //min is 0, which allows core threads to timeout, so there is no need to maintain the core thread pool
                  //If workQueue is not empty, keep at least one thread alive
                  if (min == 0 && ! workQueue.isEmpty())
                      min = 1;
                  //If the workerCount is greater than min, it means that it meets the requirements and can be returned directly.
                  if (workerCountOf(c) >= min)
                      return; // replacement not needed
              }
              //If it's done suddenly, add a worker thread for an empty task -- I don't quite understand that here.
              addWorker(null, false);
          }
      }
  1. First, determine whether the thread terminates abruptly, if it terminates abruptly, through CAS, workerCount-1
  2. Statistics the number of tasks completed by the thread pool and remove the worker from the workers
  3. Determine the state of the thread pool and try to terminate the thread pool
  4. Thread pool did not terminate successfully
    • Judging whether a task is suddenly completed is not the next step, but the third step.
    • If the core thread is allowed to timeout and the queue is not empty, at least one thread is guaranteed to survive.
    • Add a worker thread for an empty task

Worker inner class

We have already talked about the execution process and some details of thread pool execution task execute in detail. There is a word frequently appearing on the thread pool execution task execute, that is, worker instance, so what is the worker actually? What information does it contain, and how does the worker actually perform this task?

Let's introduce it in this section, or go directly to the source code:

We can see that the inner class of Worker inherits the AQS synchronizer and implements the Runnable interface, so Worker is obviously a class that can execute tasks and control interruption and lock effect.

  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;
  ​
          /** Work threads are null if the factory fails. */
          final Thread thread;
          /** Initialize tasks, possibly empty */
          Runnable firstTask;
          /** Number of completed tasks */
          volatile long completedTasks;
  ​
          /**
           * Create and initialize the first task, using thread factories to create threads
           * Initialization has three steps
           *1,Set the synchronization state of AQS to - 1 to indicate that the object needs to be waked up
           *2,Initialize the first task
           *3,Call ThreadFactory to create a thread itself and assign it to the worker's member variable thread
           */
          Worker(Runnable firstTask) {
              setState(-1); // inhibit interrupts until runWorker
              this.firstTask = firstTask;
              this.thread = getThreadFactory().newThread(this);
          }
  ​
    //Rewriting Runnable's run Method
          /** Delegates main run loop to outer runWorker  */
          public void run() {
              //Calling the runWorker method of ThreadPoolExecutor
              runWorker(this);
          }
  ​
          // Lock methods
          //
          // The value 0 represents the unlocked state.
          // The value 1 represents the locked state.
    //Represents whether exclusive locks, 0-non-exclusive 1-exclusive locks
          protected boolean isHeldExclusively() {
              return getState() != 0;
          }

    //Rewriting the tryAcquire method of AQS to try to acquire locks
          protected boolean tryAcquire(int unused) {
           //Attempt to change the synchronization state of AQS from 0 to 1
              if (compareAndSetState(0, 1)) {
               //If changed to, set the current exclusive mode thread to the current thread and return true
                  setExclusiveOwnerThread(Thread.currentThread());
                  return true;
              }
              //Otherwise return false
              return false;
          }
  ​
    //tryRelease, which rewrites AQS, tries to release the lock
          protected boolean tryRelease(int unused) {
           //Set the current exclusive mode thread to null
              setExclusiveOwnerThread(null);
              //Set AQS synchronization status to 0
              setState(0);
              //Return to true
              return true;
          }
  ​
    //Get lock
          public void lock()        { acquire(1); }
          //Attempt to acquire locks
          public boolean tryLock()  { return tryAcquire(1); }
          //Release lock
          public void unlock()      { release(1); }
          //Is it monopolized?
          public boolean isLocked() { return isHeldExclusively(); }
  ​
          void interruptIfStarted() {
              Thread t;
              if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                  try {
                      t.interrupt();
                  } catch (SecurityException ignore) {
                  }
              }
          }
  }

Summary

Writing this thread pool is really not easy. It took two weeks. There are many places in the middle that I don't understand. Moreover, the introduction of thread pool in the book "The Art of Java Concurrent Programming" is not very much. So I look very painful. I often read this method, and I don't know why to call this method and what the purpose of calling this method is. And in the process of learning, there are doubts about their learning methods, because some people told me that they do not need a sentence to analyze the source code, just need to know the process, but later still want to follow their own learning route, read more source code is always good, here I also give some suggestions to apes, when they have their own learning methods. We should stick to it in our own way.

Reference material

Fang Tengfei: The Art of Java Concurrent Programming

If you need to reproduce, please be sure to indicate the source, after all, it is not easy to move bricks one by one.

Posted by lovelf on Fri, 17 May 2019 05:22:00 -0700