Backend Development 12 min read

Understanding Java Thread States and ThreadPoolExecutor

This article explains Java thread lifecycle states, the Thread.State enum, and the design and implementation details of ThreadPoolExecutor—including its constructors, parameters, internal state constants, ctl bit manipulation, and rejection policies—while also providing code examples and usage guidance.

Top Architect
Top Architect
Top Architect
Understanding Java Thread States and ThreadPoolExecutor

Greetings from a senior architect, introducing the topic of Java thread management.

1 Thread State

Java defines several thread states in the java.lang.Thread class via the public enum State :

public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}

The meanings of each state are:

NEW : the thread has been created but not started.

RUNNABLE : the thread is executing in the JVM, possibly waiting for OS resources.

BLOCKED : the thread is waiting to acquire a monitor lock.

WAITING : the thread is waiting indefinitely for another thread to invoke Object.notify() , Object.notifyAll() , Thread.join() , or LockSupport.park() .

TIMED_WAITING : the thread is waiting for a specified timeout (e.g., Thread.sleep() , Object.wait(timeout) , Thread.join(timeout) , LockSupport.parkNanos() , LockSupport.parkUntil() ).

TERMINATED : the thread has completed execution.

The diagram below shows the transitions between these states.

2 Thread Pool

2.1 Purpose of Thread Pool

Reduce resource consumption : reuse threads instead of creating and destroying them repeatedly.

Improve response speed : tasks can be executed immediately without waiting for thread creation.

Enhance manageability : the pool provides unified management, allocation, tuning, and monitoring.

2.2 Implementation of Thread Pool

In Java, a thread pool is implemented by java.util.concurrent.ThreadPoolExecutor . Its full‑parameter constructor is:

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

The parameters mean:

corePoolSize : number of core threads kept alive (can be zero if allowCoreThreadTimeOut is enabled).

maximumPoolSize : maximum number of threads allowed.

keepAliveTime : idle time before excess threads are terminated.

unit : time unit for keepAliveTime .

workQueue : queue that holds pending Runnable tasks.

threadFactory : factory that creates new threads (defaults to DefaultThreadFactory ).

handler : rejection policy used when the queue is full and no threads are available.

A minimal constructor that omits factory and handler uses defaults:

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}

Internally, ThreadPoolExecutor uses several constants and an AtomicInteger ctl to encode both the run state (high 3 bits) and worker count (low 29 bits):

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
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;
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; }

RUNNING : pool accepts new tasks.

SHUTDOWN : pool stops accepting new tasks but processes queued ones.

STOP : pool rejects new tasks, discards queued tasks, and interrupts running tasks.

TIDYING : internal state after termination.

TERMINATED : final state after all tasks have completed.

The execution flow is:

Create a ThreadPoolExecutor (no threads are started immediately).

When a new task arrives, if the current worker count is less than corePoolSize , a new thread is created; otherwise the task is queued.

If the queue is full and the worker count is less than maximumPoolSize , a new thread is created; otherwise the rejection policy is applied.

If the worker count exceeds corePoolSize and no new tasks arrive, idle threads wait for keepAliveTime before terminating, keeping the pool size at corePoolSize .

Four built‑in rejection policies are provided:

AbortPolicy : throws a RejectedExecutionException (default).

DiscardPolicy : silently discards the task.

DiscardOldestPolicy : discards the oldest queued task and retries execution.

CallerRunsPolicy : runs the task in the calling thread.

The article also contains promotional messages, links to interview questions, and invitations to join a discussion group.

backendJavaConcurrencythreadThreadPoolExecutorJavaConcurrency
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.