Fundamentals 26 min read

Comprehensive Guide to Java Multithreading and Concurrency Utilities

This article provides an in‑depth overview of Java multithreading, covering thread creation, lifecycle methods, synchronization mechanisms, volatile semantics, thread‑local storage, high‑level concurrency utilities such as ReentrantLock, CountDownLatch, CyclicBarrier, Semaphore, Executors, Callable/Future, and atomic classes, with practical code examples and usage tips.

Java Captain
Java Captain
Java Captain
Comprehensive Guide to Java Multithreading and Concurrency Utilities

1. What Is a Thread?

A thread is an independent sub‑task that runs within a process.

2. Ways to Create a Thread

Method 1: Extend java.lang.Thread and override run().

Method 2: Implement java.lang.Runnable and provide a run() method. The second approach is recommended because it offers greater flexibility and reduces coupling.

3. Getting the Current Thread

Thread.currentThread()

4. Thread Types

Daemon threads and user threads. By default, a new thread is a user thread. Use setDaemon(true) to mark a thread as daemon.

5. Parent‑Child Relationship

A thread started from another thread becomes its child; the original thread is the parent. The main thread is the parent of any thread started from main().

6. Thread API Overview

start()

– moves the thread to the runnable state and eventually invokes run(). stop() – deprecated and unsafe; use interrupt() instead. suspend() / resume() – deprecated; avoid using them. yield() – hints that the current thread is willing to give up the CPU. join() – waits for the target thread to finish.

7. synchronized Keyword

Provides atomicity, memory visibility, and re‑entrancy. If an exception occurs inside a synchronized block, the lock is released automatically.

8. volatile Keyword

Ensures visibility of variable updates across threads but does not guarantee atomicity.

9. Thread Communication

Two main mechanisms: shared memory and explicit synchronization.

Besides synchronized, you can use Object.wait(), notify(), and notifyAll() inside a synchronized block to coordinate threads.

synchronized (lock) {
    try {
        lock.wait(); // wait for a signal, releases lock
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    // after being notified
}

synchronized (lock) {
    lock.notify(); // wake up one waiting thread
    // other operations
}

10. ThreadLocal and InheritableThreadLocal

ThreadLocal gives each thread its own isolated variable; InheritableThreadLocal allows child threads to inherit the parent’s value.

11. ReentrantLock Usage

Provides explicit lock control with optional fairness, timed try‑lock, and condition variables.

private java.util.concurrent.locks.Lock lock = new ReentrantLock();
public void method() {
    try {
        lock.lock(); // acquire lock
        // critical section
    } finally {
        lock.unlock(); // release lock
    }
}

Key features: fair vs. non‑fair lock, tryLock() with timeout, ability to query lock state, and support for multiple Condition objects.

12. ReentrantReadWriteLock

Separates read (shared) and write (exclusive) locks to improve read‑heavy performance.

private final ReadWriteLock lock = new ReentrantReadWriteLock();
public void readMethod() {
    lock.readLock().lock();
    try { /* read */ } finally { lock.readLock().unlock(); }
}
public void writeMethod() {
    lock.writeLock().lock();
    try { /* write */ } finally { lock.writeLock().unlock(); }
}

13. Synchronous vs. Concurrent Containers

Synchronous containers (e.g., Vector, Hashtable, Collections.synchronizedMap) lock the whole collection, which can become a bottleneck.

Concurrent containers (e.g., ConcurrentHashMap, CopyOnWriteArrayList, ConcurrentSkipListMap) use finer‑grained locking or lock‑free algorithms to achieve higher throughput.

14. CountDownLatch

A latch that allows one or more threads to wait until a set of operations complete.

public long timeTasks(int nThreads, final Runnable task) throws InterruptedException {
    final CountDownLatch startSignal = new CountDownLatch(1);
    final CountDownLatch doneSignal = new CountDownLatch(nThreads);
    for (int i = 0; i < nThreads; i++) {
        new Thread(() -> {
            try {
                startSignal.await();
                task.run();
            } catch (InterruptedException ignored) {}
            finally { doneSignal.countDown(); }
        }).start();
    }
    long start = System.currentTimeMillis();
    startSignal.countDown();
    doneSignal.await();
    return System.currentTimeMillis() - start;
}

15. CyclicBarrier

Blocks a set of threads until all have reached a common barrier point, then optionally runs a barrier action.

int THREAD_NUM = 5;
CyclicBarrier barrier = new CyclicBarrier(THREAD_NUM, () -> System.out.println("---Barrier Action---"));
for (int i = 0; i < THREAD_NUM; i++) {
    new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + " pre-working");
        barrier.await();
        System.out.println(Thread.currentThread().getName() + " working");
    }).start();
}

16. Semaphore

A counting semaphore controls access to a limited number of permits.

Semaphore semaphore = new Semaphore(3); // only 3 permits
semaphore.acquire(); // block if no permit
// critical section
semaphore.release(); // return permit

17. Executors Framework (Thread Pools)

Provides ready‑made thread‑pool implementations such as newSingleThreadExecutor, newFixedThreadPool, newCachedThreadPool, and scheduled executors.

ExecutorService service = Executors.newFixedThreadPool(100);
service.submit(() -> System.out.println("task"));
service.shutdown();

18. ThreadPoolExecutor Details

Constructor parameters: corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit, workQueue, threadFactory, RejectedExecutionHandler. The diagram in the original article illustrates the state transitions.

19. Callable, Future, and FutureTask

Callable<V>

can return a result or throw a checked exception. Future<V> represents the pending result of an asynchronous computation.

FutureTask<Integer> future = new FutureTask<>(() -> new Random().nextInt(100));
new Thread(future).start();
Integer result = future.get();

20. Atomic Classes

Atomic variables (e.g., AtomicInteger, AtomicLong, AtomicReference) provide lock‑free thread‑safe operations. There are also array‑based and updater‑based variants, each with specific usage constraints (non‑static, non‑final, volatile, visibility).

21. Summary

Thread safety means that concurrent execution yields the same result as sequential execution. Synchronization (via synchronized or explicit locks) and proper communication (wait/notify, Condition, pipelines) are essential to avoid data races and ensure correct program behavior.

For more Java concurrency tips, follow the "Java团长" WeChat channel.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

ThreadLockExecutorService
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

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.