preface

I thought I’d write a blog post about the whole thread pool, but then I realized. There’s a lot to be said for thread pools. So the author will be divided into several series of slowly analysis.

This article focuses on the state of thread pools. Without further ado. Get on the car start

series

  • Thread Pool Series – (1) Background
  • Thread pool series – (2) Thread pool status
  • Thread pool series – (3) Rejection policy
  • Thread Pool series – (4) Workflow
  • Thread pool series – (5) Shutdown && shutdownNow
  • Thread pool – (6) Submit

Five operating states

You can see this in the thread pool. It has five internal states. As shown below.

Using the Java thread pool implementation principle and its practice in Meituan business, each state is explained as shown in the figure below.

The state transformation process is as follows

How to Maintain state

Given these states, how does the thread pool record this state? This is where thread pool design comes in handy.

The CTL variable in the thread pool stores the runState and the number of threads (workerCount).

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

We know that. An int takes four bytes. 32. The thread pool uses the first three bits of an int as the runState and the last 29 bits as the number of threads. So how does the thread pool operate to store two states with one variable?

So the first thing we know is that the first three bits are states. The last 29 bits are the number of threads. You can also see the definition inside the thread pool in the code. The result of COUNT_BITS is 29.

//Integer.SIZE = 32
//COUNT_BITS = 29
private static final int COUNT_BITS = Integer.SIZE - 3;
Copy the code

I know some conceptual things. Let’s go back and look at the state of the thread pool. The author compiled a table as follows. Let’s focus on the first three.

state calculation The calculation results Top three
RUNNING -1 << COUNT_BITS 1110 0000 0000 0000 0000 0000 111
SHUTDOWN 0 << COUNT_BITS 0000 0000 0000 0000 0000 0000 000
STOP 1 << COUNT_BITS 0010 0000 0000 0000 0000 0000 001
TIDYING 2 << COUNT_BITS 0100 0000 0000 0000 0000 0000 010
TERMINATED 3 << COUNT_BITS 0110 0000 0000 0000 0000 0000 0000 011

How to understand CAPACITY

The following figure shows the count of threads inside the thread pool. Thread state and methods for updating state. You can see that both runStateof and workerCountOf operate on CAPACITY.

So what is CAPACITY? In the figure below. We see. You can see it in the thread pool. CAPACITY is an int.

Ok~. Take the code out. Let’s look at a line of code like this. Have a look. I don’t know what this is.

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

1 << COUNT_BITS. We know that COUNT_BITS is 29, so the result of 1 << COUNT_BITS should be binary

0010 0000 0000 0000 0000 0000Copy the code

Minus 1 so the binary is going to be 1

0001 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111Copy the code

In the picture above. Will it be visible to the eye

1 << COUNT_BITS (1 << COUNT_BITS) – 1

Eumm will end up with a number where the last 29 is 1 and the first three are 0.

Count the number of thread pools

You know what CAPACITY is. How do we count the number of threads

private static int workerCountOf(int c)  { return c & CAPACITY; }
Copy the code

We know that the first three bits of CAPACITY are 0 and the last 29 bits are 1. That’s the same thing as going to the next 29 places. Isn’t that clever?

Calculating thread state

Looking at how to calculate thread state.

private static int runStateOf(int c)     { return c & ~CAPACITY; }
Copy the code

It’s also very clever design. Perform non-operations on CAPACITY first

Used to be

0001 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111Copy the code

Now it becomes

1110 0000 0000 0000 0000 0000Copy the code

And then in and operation. That’s the same thing as taking the first three places. Eumm SEC –

Update thread pool state and thread count

private static int ctlOf(int rs, int wc) { return rs | wc; }
Copy the code

For example, when the initial thread pool is initialized. CtlOf (RUNNING, 0)

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

Let’s see how it works. If one of the two bits is 1, the result is 1, otherwise it is 0

1110 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000Copy the code

The calculation results are as follows

1110 0000 0000 0000 0000 0000Copy the code

So when the thread pool is created. Its state is RUNNING and the number of threads is 0

If the thread pool is working. The number of threads has changed. Not by ctlOf method to update ha. There are special ways to update.

private boolean compareAndIncrementWorkerCount(int expect) {
    return ctl.compareAndSet(expect, expect + 1);
}
Copy the code

Why is that

Why bother. Use a variable to maintain two states. The author also summed up the experience of people before.

  • If I have two variables. Then you need to occupy more resources.
  • Two variables are more difficult to maintain. In multithreading. To maintain two variables. Lock resources are bound to be occupied.
  • There are many places in the thread pool where you need to determine both thread state and thread count. This is also designed to prevent unexpected state inconsistencies from occurring when judging.

One more detail. ThreadPoolExecutor defines a variable CTL whose type is AtomicInteger. This atomic class is thread-safe. There are no errors due to multithreading.

How to switch states

One can plant trees, and another can enjoy the shade. The following chart is a summary of the predecessors. I’m going to put it right here

In the above author cited a 🌰. The state is synchronized during initialization. Here’s the code.

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

Through the code and how the state was synchronized earlier. We know that. The number of threads is 0. The thread status is RUNNING.

You can actually see this in the flow above. State change. A difference occurs when shutdown and shutdownNow are executed. About these two methods. I will introduce it later.

Take a look at the shutdown method

Here you see advanceRunState(SHUTDOWN); Methods. Click in to see that it updates the state using the ctlOf method

AdvanceRunState method source code
private void advanceRunState(int targetState) {
    // assert targetState == SHUTDOWN || targetState == STOP;
    for (;;) {
        int c = ctl.get();
        if (runStateAtLeast(c, targetState) ||
            ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
            break; }}Copy the code

Look again at the tryTerminate method. The box below is where the status update will take place.

Again, shutdownNow has a different state in the box than Shutdown.

Where state judgments are used in the source code

Someone did. I know what you said. So where do you judge that. What is the function of judgment.

In the first place. Thread pools are not as complicated as you might think. As a pool. Its greatest use is to reuse threads. State inside the thread pool. Just to see if the current pool is available. Is it in the running state? If it’s not in this state. The thread pool is not available. Can’t use thread pool. Simple. If it’s not available, it’s not available. Thread pools have their own rejection policies. The default is to throw an exception. You can also see it in the source code

The last

This article is mainly about states. Thread pools are difficult to say. Simple and easy to say. The author will still analyze and summarize from different points. Of course there are wrong points in the article. Criticisms and corrections are welcome.