Java Concurrency: Threads, Locks, Thread Pools & CompletableFuture

Creating threads, synchronized and volatile, ReentrantLock, ThreadPoolExecutor parameters, and composing async work with CompletableFuture — the concurrency knowledge backend interviews demand.

AdvancedUpdated 2026-10-28102K views

Three Ways to Create a Thread

// 1. 继承 Thread
Thread t1 = new Thread() {
    public void run() { System.out.println("t1"); }
};
t1.start();

// 2. 实现 Runnable(推荐)
Thread t2 = new Thread(() -> System.out.println("t2"));
t2.start();

// 3. Callable + FutureTask:带返回值
FutureTask<Integer> ft = new FutureTask<>(() -> 42);
new Thread(ft).start();
System.out.println(ft.get());     // 阻塞等结果
start() creates the OS thread and calls run(); calling run() directly just runs synchronously on the current thread. Never start the same Thread twice.

synchronized and volatile

synchronized provides both mutual exclusion and visibility; volatile only guarantees visibility (no interleaving of reads/writes) but not atomicity of compound operations like i++.

synchronized void inc() { count++; }          // 锁 this
static synchronized void inc2() { c2++; }    // 锁 Class 对象
Object lock = new Object();
synchronized (lock) { /* critical section */ }

// volatile:轻量可见性
private volatile boolean running = true;
public void stop() { running = false; }       // 其他线程立即可见

// 原子类(CAS,无锁)
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();
counter.compareAndSet(0, 1);

ReentrantLock vs synchronized

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // critical section
} finally {
    lock.unlock();             // 必须在 finally 释放
}
// 公平锁、可中断、tryLock 超时是 synchronized 没有的能力
lock.tryLock(3, TimeUnit.SECONDS);
  • synchronized: simpler, JVM-optimized (biased locking removed in 15+, sync eras), cannot time out.
  • ReentrantLock: fair ordering, lockInterruptibly, tryLock with timeout, multiple Condition queues.

Thread Pools: ThreadPoolExecutor

Never use newFixedThreadPool / newCachedThreadPool blindly in production — their unbounded queues or unbounded threads can exhaust memory. Understand the 7 parameters of ThreadPoolExecutor.

ThreadPoolExecutor pool = new ThreadPoolExecutor(
    4,                          // corePoolSize  核心线程数
    16,                         // maximumPoolSize 最大线程数
    60, TimeUnit.SECONDS,       // keepAliveTime 空闲回收时间
    new LinkedBlockingQueue<>(200),  // workQueue 任务队列(有界!)
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略
);
pool.submit(() -> doWork());
pool.shutdown();
  1. Task arrives: if threads < corePoolSize, create a core thread.
  2. Otherwise it is queued; if the queue is full and threads < max, create a non-core thread.
  3. If both are exhausted, the rejection policy runs: Abort throws, CallerRuns runs on the caller, Discard drops.
Size rule of thumb: CPU-bound tasks -> N_cores + 1 threads; IO-bound (DB, HTTP) -> 2 * N_cores or estimate with (target QPS * avg latency in s).

CompletableFuture for Async Composition

Executor io = Executors.newFixedThreadPool(16);

CompletableFuture<User> userFuture =
    CompletableFuture.supplyAsync(() -> userService.findById(1L), io);

CompletableFuture<Order> orderFuture =
    CompletableFuture.supplyAsync(() -> orderService.findLatest(1L), io);

// 两个异步任务都完成后合并结果
userFuture.thenCombineAsync(orderFuture, (u, o) -> {
    Profile p = new Profile();
    p.setUser(u); p.setOrder(o);
    return p;
}, io).thenAccept(System.out::println);

// 超时与异常
orderFuture.orTimeout(2, TimeUnit.SECONDS)
           .exceptionally(ex -> Order.empty());

Friends & Partners