Java Threads

Multithreading was designed into Java from the very beginning. You’ll see support for multithreading in special keywords and in a number of classes in the Java Standard API. Understanding threading is a very important part of being an accomplished Java developer.

Overview

Every thread in the JVM is represented by a Java object of class Thread. There are many other classes, records, interfaces, and enums used in conjunction with threads. A subset of these are:

PackageClasses
java.langThread Runnable ThreadGroup Thread.State Thread.Builder Thread.Builder.OfPlatform Thread.Builder.OfVirtual ThreadLocal InheritableThreadLocal Thread.UncaughtExceptionHandler InterruptedException IllegalThreadStateException IllegalMonitorStateException WrongThreadException
java.util.concurrent BlockingQueue<E> ArrayBlockingQueue<E> LinkedBlockingQueue<E> ConcurrentLinkedQueue<E> TransferQueue<E> LinkedTransferQueue<E> PriorityBlockingQueue<E> SynchronousQueue<E> DelayQueue<E> LinkedBlockingDeque<E> ConcurrentLinkedDeque<E> ConcurrentSkipListSet<E> CopyOnWriteArraySet<E> ConcurrentMap<K,V> ConcurrentNavigableMap<K,V> ConcurrentHashMap<K,V> ConcurrentHashMap.KeySetView<K,V> ConcurrentSkipListMap<K,V> CopyOnWriteArrayList<E>

Executor ExecutorService AbstractExecutorService ScheduledExecutorService Executors ForkJoinPool ThreadPoolExecutor ScheduledThreadPoolExecutor CompletionService<V> ExecutorCompletionService<V> ThreadFactory ForkJoinPool.ForkJoinWorkerThreadFactory ForkJoinPool.ManagedBlocker

CountDownLatch CyclicBarrier Exchanger<V> Semaphore Phaser

ForkJoinTask<V> RecursiveTask<V> FutureTask<V> ForkJoinWorkerThread RecursiveAction ThreadLocalRandom SubmissionPublisher<T> CompletionStage<T> CompletableFuture<T> CountedCompleter<T>

Callable<V> Delayed Future<V> RunnableFuture<V> RunnableScheduledFuture<V> ScheduledFuture<V> RunnableScheduledFuture<V> CompletableFuture.AsynchronousCompletionTask Flow.Publisher<T> Flow.Subscriber<T> Flow.Processor<T,R> Flow.Subscription

BrokenBarrierException ExecutionException CompletionException CancellationException RejectedExecutionException TimeoutException RejectedExecutionHandler ThreadPoolExecutor.AbortPolicy ThreadPoolExecutor.CallerRunsPolicy ThreadPoolExecutor.DiscardOldestPolicy ThreadPoolExecutor.DiscardPolicy
java.util.concurrent.locksLock ReentrantLock ReadWriteLock StampedLock ReentrantReadWriteLock Condition LockSupport
java.util.concurrent.atomicAtomicInteger AtomicLong AtomicBoolean AtomicReference AtomicIntegerArray AtomicLongArray AtomicReferenceArray AtomicMarkableReference  AtomicStampedReference AtomicIntegerFieldUpdater AtomicLongFieldUpdater AtomicReferenceFieldUpdater DoubleAccumulator LongAccumulator DoubleAdder LongAdder

Thread Objects

Every thread in the JVM is represented by a Java object of class Thread. A thread is an object containing a task to run, an id, a name, its current state, a reference to the group it belongs to (if any), its priority, its daemon status, its interrupt status, and more.

This is a partial view, omitting (1) most of the fields, (2) the little-used or overly-technical members, and (3) the constructors, which you should not call directly anyway, of the class java.lang.thread:

package java.lang;
public class Thread implements Runnable {
    public static final int MIN_PRIORITY = 1;
    public static final int NORM_PRIORITY = 5;
    public static final int MAX_PRIORITY = 10;

    public static native Thread currentThread() { ... }
    public static boolean interrupted() { ... }
    public static void yield() { ... }
    public static void sleep(long millis) throws InterruptedException { ... }
    public static void sleep(Duration duration) throws InterruptedException { ... }
    public static void dumpStack() { ... }

    public sealed interface Builder permits Builder.OfPlatform, Builder.OfVirtual {
        Builder name(String name);
        Builder name(String prefix, long start);
        Builder inheritInheritableThreadLocals(boolean inherit);
        Builder uncaughtExceptionHandler(UncaughtExceptionHandler ueh);
        Thread unstarted(Runnable task);
        Thread start(Runnable task);
        ThreadFactory factory();
        sealed interface OfPlatform extends Builder permits ThreadBuilders.PlatformThreadBuilder {
            @Override OfPlatform name(String name);
            @Override OfPlatform name(String prefix, long start);
            @Override OfPlatform inheritInheritableThreadLocals(boolean inherit);
            @Override OfPlatform uncaughtExceptionHandler(UncaughtExceptionHandler ueh);
            OfPlatform group(ThreadGroup group);
            OfPlatform daemon(boolean on);
            default OfPlatform daemon() { return daemon(true); }
            OfPlatform priority(int priority);
            OfPlatform stackSize(long stackSize);
        }
        sealed interface OfVirtual extends Builder permits ThreadBuilders.VirtualThreadBuilder {
            @Override OfVirtual name(String name);
            @Override OfVirtual name(String prefix, long start);
            @Override OfVirtual inheritInheritableThreadLocals(boolean inherit);
            @Override OfVirtual uncaughtExceptionHandler(UncaughtExceptionHandler ueh);
        }
    }

    public static Builder.OfPlatform ofPlatform() { return new ThreadBuilders.PlatformThreadBuilder(); }
    public static Builder.OfVirtual ofVirtual() { return new ThreadBuilders.VirtualThreadBuilder(); }
    public static Thread startVirtualThread(Runnable task) { ... }
    public static int activeCount() { ... }

    public final long threadId() { ... }
    public final ThreadGroup getThreadGroup() { ... }
    public final boolean isVirtual() { ... }
    public final boolean isAlive() { ... }
    public void start() { ... }
    public void run() { ... }
    public void interrupt() { ... }
    public boolean isInterrupted() { ... }
    public final void setPriority(int newPriority) { ... }
    public final int getPriority() { ... }
    public final synchronized void setName(String name) { ... }
    public final String getName() { ... }
    public final void join() throws InterruptedException { ... }
    public final void join(long millis) throws InterruptedException { ... }
    public final boolean join(Duration duration) throws InterruptedException { ... }
    public final void setDaemon(boolean on) { ... }
    public final boolean isDaemon() { ... }
    public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
    public State getState() { ... }
    public String toString() { ... }

    @FunctionalInterface public interface UncaughtExceptionHandler { ... }
    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler ueh) { ... }
    public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() { ... }
    public UncaughtExceptionHandler getUncaughtExceptionHandler() { ... }
    public void setUncaughtExceptionHandler(UncaughtExceptionHandler ueh) { ... }
}

The most important properties of a thread are:

When the JVM starts, six threads are created and launched (and housed in three thread groups):

ThreadGroup "system": max priority=10
    Thread 14 "Reference Handler": priority=10, platform, daemon, RUNNABLE
    Thread 15 "Finalizer": priority=8, platform, daemon, WAITING
    Thread 16 "Signal Dispatcher": priority=9, platform, daemon, RUNNABLE
    Thread 23 "Notification Thread": priority=9, platform, daemon, RUNNABLE
    ThreadGroup "main": max priority=10
        Thread 3 "main": priority=5, platform, RUNNABLE
    ThreadGroup "InnocuousThreadGroup": max priority=10
        Thread 24 "Common-Cleaner": priority=8, platform, daemon, TIMED_WAITING

To fully learn everything there is to know about Java threads, you can do no better that to actually read the source code of the class itself, or the documentation generated straight from the source code.

Exercise: Seriously, browse that documentation now.

If you’re still here and want to read these notes, be aware that nny information below that tries to explain the Thread class is probably only a rough approximation and may be wrong or out of date.

Platform vs. Virtual Threads

There are two kinds of threads.

Typically, platform threads are best for compute-intensive tasks, while virtual threads are best for I/O-bound tasks. Virtual threads are designed to be lightweight and can be created in massively large numbers without overwhelming the system.

Exercise: To learn more, read the documentation, or ask an AI chatbot for explanations and examples.
Exercise: Find out when and why virtual threads were introduced in Java.

For more on virtual threads, see The Ultimate Guide to Java Virtual Threads and Oracle’s Virtual Threads page in the Java Core Libraries documentation.

Thread States

The six thread states are:

NEW
The thread has not yet started.
RUNNABLE
The thread is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.
BLOCKED
The thread is waiting for a monitor lock to enter a synchronized block/method or reenter a synchronized block/method after calling Object.wait.
WAITING
The thread is waiting due to calling one of the following methods:
  • Object.wait with no timeout
  • Thread.join with no timeout
  • LockSupport.park
TIMED_WAITING
The thread is waiting due to calling one of the following methods with a specified positive waiting time:
  • Thread.sleep
  • Object.wait with timeout
  • Thread.join with timeout
  • LockSupport.parkNanos
  • LockSupport.parkUntil
TERMINATED
The thread has completed execution.
Exercise: Draw a state diagram for a Java thread, using the six states above. You’ll have to do some research to get the transitions (and their triggers) just right.

Some operations throw IllegalThreadStateException if called on a thread in a state incompatible with the operation, while others will do nothing at all in that case.

Creating and Starting a Thread

The code that a Java thread actually runs is called a task. A task is an object implementing the functional interface Runnable, whose method is void run(). Although there are constructors in the Thread class for creating threads directly, the best practice is not to directly use the constructors. You should instead either:

For now, let’s look only at the builder approach. Since Runnable is a functional interface, we can write a task as a lambda:

var t1 = Thread.ofPlatform().start(() -> { ... });
var t2 = Thread.ofVirtual().start(() -> { ... });

var t3 = Thread.ofPlatform().unstarted(() -> { ... });
var t4 = Thread.ofVirtual().unstarted(() -> { ... });

There’s a shortcut for t2 (only): Thread.startVirtualThread(() -> { ... });

You don’t have to use a lambda. In fact, if the tasks have a lot of internal state, or you have a bunch of related tasks to run on separate threads, you should implement Runnable in your own class:

BarkingDogs.java
public class BarkingDogs {
    record Dog(String name, int barkCount) implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < barkCount; i++) {
                System.out.println(name + " says WOOF!");
            }
            System.out.println(name + " finished barking.");
        }
    }

    public static void main(String[] args) {
        Thread.ofPlatform().start(new Dog("Rex", 8));
        Thread.ofPlatform().start(new Dog("Roxy", 13));
        Thread.ofPlatform().start(new Dog("Max", 21));
    }
}

The builders allow you to initialize the thread with a bunch of properties:

var t5 = Thread.ofPlatform()
    .name("LisichkaThread")
    .daemon(true)
    .group(bestDogsGroup)
    .priority(Thread.MAX_PRIORITY)
    .uncaughtExceptionHandler((t, e) -> {
        System.err.println("Lisichka is in trouble: " + e);
    })
    .start(new Dog("Lisichka", 5));
Not familiar with the Builder pattern?

It’s actually quite cool. You start with a builder object, then call zero or more methods to add properties into the builder, then call a terminal method to create an object from all the properties you collected into the builder.

Should I really not use the Thread constructors?

Well, the Thread constructors that have been in Java since 1995. The builders arrived in September of 2023, so most of the web pages out there offering help will show you how to use the threads directly. In all new code, however, use the builders when you want to make one or two threads.

If you need to create a family of threads, an executor service is the way to go. Those have been around since 2004.

Exercise: Actually, thread factories are okay too! Research how to use them.

To be fair, Java tasks don’t have to only be Runnable objects; they can also be Callable objects, which return a value. But the thread builders only accept Runnable tasks, so you’ll have to use an executor service if you want to run a Callable.

Stopping a Thread

A thread stops running when:

Daemon and Non-daemon threads.

Every thread has a boolean property signifying whether it is or is not a daemon thread. If a thread is a daemon thread, the JVM will be happy to exit without waiting for the thread to terminate. Daemon threads are good for “servers” that listen and provide services on demand. Non-daemon threads do active work. The JVM will not exit until all non-daemon threads have terminated.

How do you forcibly stop a thread? Answer: periodically examine a flag.

class Worker implements Runnable {
    private volatile boolean running = true;

    public void stop() {
        running = false;
    }

    public void run() {
        while (running) {
            // Do work
        }
    }
}

Uncaught Exceptions

Normally you’d want to wrap the body of the run method in a try-finally block to make sure you clean up in case an exception was thrown. If you wanted to react to an exception you’d need catch-clauses as well. All run methods would have to be structured this way.

An alternative is to install an UncaughtExceptionHandler for a thread or thread group. Or set the application-wide default uncaught exception handler. This example should explain it all:

UncaughtExceptionDemo.java
/**
 * An application illustrating the use of uncaught exception handlers.
 * if an uncaught exception is thrown in a thread, the system first
 * tries the thread's uncaught exception handler; if that is missing,
 * it tries the thread's thread group's handler; if that fails it tries
 * the default uncaught exception handler.
 */
public class UncaughtExceptionDemo {

    private static ThreadGroup group1 = new ThreadGroup("group1") {
        public void uncaughtException(Thread t, Throwable x) {
            System.out.println(
                t.getName() + " deferred to its group to handle uncaught " + x);
        }
    };

    // A thread with its own uncaught exception handler.
    private static Thread thread1 = Thread.ofPlatform()
        .group(group1)
        .name("thread1")
        .uncaughtExceptionHandler(
            new Thread.UncaughtExceptionHandler() {
                public void uncaughtException(Thread t, Throwable x) {
                    System.out.println(
                        t.getName() + " handled its own uncaught " + x);
                }
            }
        ).unstarted(() -> {throw new RuntimeException();});


    // A thread with no uncaught exception handler but whose group has one.
    private static Thread thread2 = Thread.ofPlatform()
        .group(group1)
        .name("thread2")
        .unstarted(() -> {throw new RuntimeException();});

    // A thread with no uncaught exception handler and in no group.
    private static Thread thread3 = Thread.ofPlatform()
        .name("thread3")
        .unstarted(() -> {throw new RuntimeException();});

    public static void main(String[] args) {
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            public void uncaughtException(Thread t, Throwable x) {
                System.out.println(
                    t.getName() + " invoked the default handler for uncaught " + x);
            }
        });
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

Output:

thread3 invoked the default handler for uncaught java.lang.RuntimeException
thread1 handled its own uncaught java.lang.RuntimeException
thread2 deferred to its group to handle uncaught java.lang.RuntimeException

Synchronization

In general, getting actions in a multithreaded program to happen in some correct order is hard because:

The JLS formally specifies a memory model and defines kinds of actions (read, write, synchronization, external, and thread divergence) so as to formally define a orderings of actions that help (see Section 17.4 of the specification). While there are many details, the most useful things to know are:

Java developers have a lot of choices for synchronizing:

The synchronized Statement

Every object has a monitor which can be locked an unlocked. The monitor can only be owned by one thread at a time. If the monitor is owned by t1 and a different thread t2 wants it, t2 blocks. When the monitor gets unlocked, the threads blocked on the monitor compete for it and only one of them gets it.

The monitor for object o is only acquired by executing a synchronized statement on o:

    synchronized (o) {
        ...
    }

The lock is freed at the end of the statement, whether it completes normally or via an exception.

You can mark methods synchronized:

class C {
    synchronized void p() {...}
    static synchronized void q() {...}
    ...
}

but this is just syntactic sugar for:

class C {
    void p() {synchronized (this) {...}}
    static void q() {synchronized(C.class) {...}}
    ...
}

The synchronized statement is used to enforce mutual exclusion. When multiple threads access the same piece of data, it’s imperative that one thread not see the data in an intermediate state. Example:

class PriorityQueue {
    // Implemented as a binary heap
    private Object[] data;
    private int size;
    ...
    public synchronized add(Object o) {
        if (size == data.length) {
            throw new HeapFullException();
        }
        data[size++] = o;
        siftUp();
    }
    public synchronized Object remove() {
        ...
    }
    public synchronized String toString() {
        ...
    }
}

If the methods add() and remove() were not synchronized several problems could occur.

Exercise: Show how leaving these methods unsynchronized can result in (1) the incorrect item being removed, and (2) toString() showing duplicates. (To do this exercise, you need to know how binary heaps work.)
Generally you have to synchronize them all

In the above example, if add() is synchronized and remove() is not, they can still both be running at the same time!

Volatile Fields

You might be able to avoid locking the object and still have mutual exclusion in the case in which the shared data is a single field. This is because Java guarantees loading and storing of variables (except longs and doubles) are atomic — there’s no "intermediate state" during a store nor can it be changed in the middle of a load. (Note only loads and stores are atomic, an expression like x++ is not.)

However, threads can hold variables in registers. If a shared variable is in a register, one thread won’t be able to see another thread’s change to it. The volatile keyword is there to prevent that. The Java Language Specification states that “A field may be declared volatile, in which case the Java memory model ensures that all threads see a consistent value for the variable.”

One policy that an implementation may take is that for fields marked volatile,
  • All reads must come from main memory
  • All writes must go to main memory

A volatile field can let you avoid a synchronized statement and the associated lock contention. (This works for longs and doubles, too: marking them volatile not only ensures threads see consistent updates but it forces atomicity where none existed before! See the JLS, section 17.7.)

Exercise: What happens if you make a field both final and volatile? Why does this happen?

Explicit Locks

If your synchronization requirements are very complex, explicit locks might be more convenient. Read the Javadocs for the package java.util.concurrent.locks; they are extensive and quite decent.

lockuml.png

Basic Usage

Rule #1: unlock the lock in a finally block!

    Lock l = ...;
    l.lock();
    try {
        // access the resource protected by this lock
    } finally {
        l.unlock();
    }
    Lock l = ...;
    if (l.trylock()) {
       // Got it
       try {...} finally {l.unlock();}
    } else {
       // Didn’t get it right away, do something else
       ...
    }
    Lock l = ...;
    if (l.trylock(3000, TimeUnit.MILLISECONDS)) {
       // Got it
       try {...} finally {l.unlock();}
    } else {
       // Didn’t get it within 3 seconds, do something else
       ...
    }
Exercise: Give a usage pattern for lockInterruptibly();

The ReentrantLock class

The Java Core API provides one class for single-owner locks:

package java.util.concurrent.locks;
public class ReentrantLock implements Lock, java.io.Serializable {
    public ReentrantLock() {...}
    public ReentrantLock(boolean fair) {...}
    public void lock() {...}
    public void lockInterruptibly() throws InterruptedException {...}
    public boolean tryLock() {...}
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {...}
    public void unlock() {...}
    public Condition newCondition() {...}
    public int getHoldCount() {...}
    public boolean isHeldByCurrentThread() {...}
    public boolean isLocked() {...}
    public final boolean isFair() {...}
    protected Thread getOwner() {...}
    public final boolean hasQueuedThreads() {...}
    public final boolean hasQueuedThread(Thread thread) {...}
    public final int getQueueLength() {...}
    protected Collection<Thread> getQueuedThreads() {...}
    public boolean hasWaiters(Condition condition) {...}
    public int getWaitQueueLength(Condition condition) {...}
    protected Collection<Thread> getWaitingThreads(Condition condition) {...}
    public String toString() {...}
}

“Reentrant” means a thread can obtain the lock more than once without deadlocking (the system keeps a hold count, but it’s limited to 2147483648)

The parameter fair in the constructor means waiters are queued FIFO. It doesn’t mean anything else. For example another thread executing tryLock() might jump ahead of all waiters.

ReadWrite Locks

A ReadWriteLock contains a pair of locks: one exclusive write lock and one non-exclusive read lock.

package java.util.concurrent.locks;
public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

In theory, allowing multiple readers should increase throughput, but only if you really have multiple processors.

It’s best for cases with infrequently modified but heavily searched data.

If read operations are short the lock overhead will be noticeable, so you might want to use a regular mutex lock for both reads and writes.

There are many different variations on how read write locks can work: see the Javadocs for a good discussion.

Explicit vs. Implicit Locking

Implicit Locks Explicit Locks
  • Are acquired only via synchronized statements (or methods)
  • Can only be used lexically
  • Can only be acquired and released in a LIFO way
  • Prevents common mistakes like forgetting to unlock
  • Are always blocking (and not iterruptibly so)
  • Are always reentrant
  • Cannot queue waiters fairly
  • Are pretty limited and inflexible, but easy and safe
  • Very flexible
  • Can lock and unlock pretty much at any time (for example the lock and unlock calls can be in different methods)
  • Can be associated with multiple condition variables
  • Can be programmed to allow exclusive access to a shared resource or concurrent access to a shared resource
  • Can participate in hand-over-hand (chain) locking
  • Support non-blocking conditional acquisition
  • Support a time-out acquisition attempt
  • Support an interruptable acquisition attempt
  • Must be used responsibly
  • Can be made to be non-reentrant
  • Can be made fair
  • Can be made to support deadlock detection

Condition Synchronization

A common synchronization issue arises when you have to modify a shared resource only if some condition holds. But you need the object lock to combine the check and update. However if the condition does not hold you want to block until it does. But you can’t wait for the condition while holding the lock, so there’s some built-in support for all this.

There are ways to do this with both implicit and explicit locks.

Object.wait and Object.notify

The most straightforward way to implement this in Java is to use the wait and notify methods of the class Object. Here's an implementation of a blocking queue:

public class BlockingQueue {
    private Object[] data;
    private int head = 0;
    private int tail = 0;
    private int count = 0;
    public Buffer(int capacity) {
        data = new Object[capacity];
    }
    public synchronized void add(Object item) throws InterruptedException {
        while (count == data.length) {wait();}
        data[tail] = item;
        tail = (tail + 1) % data.length;
        count++;
        notifyAll();
    }
    public synchronized Object remove() throws InterruptedException {
        while (count == 0) {wait();}
        Object item = data[head];
        head = (head + 1) % data.length;
        count--;
        notifyAll();
        return item;
    }
    public synchronized int size() {
        return count;
    }
}

See the JLS for the semantics of wait and notify, or see the Javadocs for class Object for a slightly less formal treatment. The high-level explanation is:

The Condition Interface

Condition objects give wait and notify functionality to explicit locks the same way wait() and notify() work with implicit locks. This gives us a situation where the add threads and the remove threads are in separate wait sets! Example:

class BlockingQueue {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull  = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private final Object[] data;
    private int head = 0;
    private int tail = 0;
    private int count = 0;

    public void add(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) notFull.await();
            data[tail] = item;
            tail = (tail + 1) % data.length;
            count++;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public Object remove() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) notEmpty.await();
            Object item = data[head];
            head = (head + 1) % data.length;
            count--;
            notFull.signal();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

Good to know

Synchronization Objects

There are some cool little things you can use in place of locks and conditions. Most are objects that hand out permits. A thread blocks until a permit is available.

CountDownLatch

Threads wait on a countdown latch until the count goes down to zero. Then they pass.

package java.util.concurrent;
public class CountDownLatch {
    public CountDownLatch(int count) {...}
    public void await() throws InterruptedException {...}
    public boolean await(long timeout, TimeUnit unit) throws InterruptedException {...}
    public void countDown() {...}
    public long getCount() {...} // just for debugging and testing
    public String toString() {...}
}

Details:

There are good examples in the Javadocs.

CyclicBarrier

A number of threads meet (block) at the barrier, and when the last thread in the party arrives, they are all released. The barrier object can be reused after the threads have been released.

package java.util.concurrent;
public class CyclicBarrier {
    public CyclicBarrier(int parties, Runnable barrierAction) {...}
    public CyclicBarrier(int parties) {...}
    public int getParties() {...}
    public int await() throws InterruptedException, BrokenBarrierException {...}
    public int await(long timeout, TimeUnit unit) throws InterruptedException,
        BrokenBarrierException, TimeoutException {...}
    public boolean isBroken() {...}
    public void reset() {...}
    public int getNumberWaiting() {...} // just for debugging
}

Details:

There are good examples in the Javadocs.

Exchanger

An exchanger is used as a synchronization point at which two threads can exchange objects. Each thread presents some object on entry to the exchange method, and receives the object presented by the other thread on return.

package java.util.concurrent;
public class Exchanger<V> {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition taken = lock.newCondition();
    private V item;
    .
    .
    .
    public Exchanger() {...}
    public V exchange(V x) throws InterruptedException {...}
    public V exchange(V x, long timeout, TimeUnit unit)
        throws InterruptedException, TimeoutException {...}
}

Good to know

There are good examples in the Javadocs.

Semaphore

A semaphore maintains a counter of available permits; threads can block on a semaphore until there's a permit available. There are ways to acquire conditionally, interruptibly, or subject to a time out.

package java.util.concurrent;
public class Semaphore implements java.io.Serializable {
    public Semaphore(int permits) {...}
    public Semaphore(int permits, boolean fair) {...}
    public void acquire() throws InterruptedException {...}
    public void acquireUninterruptibly() {...}
    public boolean tryAcquire() {...}
    public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException {...}
    public void release() {...}
    public void acquire(int permits) throws InterruptedException {...}
    public void acquireUninterruptibly(int permits) {...}
    public boolean tryAcquire(int permits) {...}
    public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
        throws InterruptedException {...}
    public void release(int permits) {...}
    public int availablePermits() {...}
    public int drainPermits() {...} // acquire and return them all, return num acquired
    protected void reducePermits(int reduction) {...} // this does not block
    public boolean isFair() {...}
    public final boolean hasQueuedThreads() {...} // don't rely on it
    public final int getQueueLength() {...} // just an estimate; don't rely on it
    protected Collection<Thread> getQueuedThreads() {...} // just an estimate; don't rely on it
    public String toString() {...}
}

Good to know:

There are good examples in the Javadocs.

Atomic Classes

The package java.util.concurrent.atomic has a bunch of classes to make accessing single variables thread-safe without using locks. (Native methods are used instead.)

The classes:

Good to know:

Interrupting

Thread t1 can call t2.interrupt() to interrupt t2 from blocking on something:

If t is blocking on... Then t.interrupt() ... and t‘s interrupt status gets set to...
Object.wait
Thread.join
Thread.sleep
completes the blocking call and makes t get an InterruptedException false
an I/O operation on an interruptable channel closes the channel and makes t get a ClosedByInterruptException true
a selector completes the call (just like wakeup) true
nothing does nothing true

Checking the interrupt status

public static boolean interrupted()
Returns whether the current thread's interrupt status is true, and clears the status too!
public boolean isInterrupted()
Returns whether the thread in question's interrupt status is true (without affecting the status at all)

Suspension

A thread can voluntarily

But what if we want one thread to force another thread to suspend execution? You can use the same idea we saw above to make one thread stop another, that is, have the suspendable thread periodically check if it should be suspended and block on a condition until woken up.

You can also use the LockSupport class, which is a low-level utility for blocking and unblocking threads. It provides a way to park a thread until it is unparked by another thread.

public class LockSupport {
    private LockSupport() {} // Cannot be instantiated.
    public static void park() {...}
    public static void parkNanos(long nanos) {...}
    public static void parkUntil(long deadline) {...}
    public static void unpark(Thread thread) {...}
}

Good to know

The Javadocs have a good example of how to implement a FIFO mutex with the LockSupport methods.

Thread Locals

You should read Brian Goetz’s article on ThreadLocals. It's better than anything I could write.

Exercise: Do ThreadLocals buy you anything over just making objects local to each thread’s runnable’s run() method?

Threads and Collections

Most of the time when threads share data, they're not just sharing a single variable. And as you know, updating a collection is rarely an atomic operation. There's lots of potential for races; there's also the potential for writing inefficient code with too much locking.

Java has a relatively huge API for collections. To get started, see my notes on collections These notes feature links to the documentation and tutorial articles. I've also included there a table of concurrency properties for each of the collection classes.

Threads and Graphics

Java has a graphics library called Swing.

Graphics-intensive code pretty much requires threading: GUI programs are event-driven, and event handling and painting need to happen on a separate thread from your computations lest your computations prevent repainting. This means if you do run code on this thread, it better not take too long.

Rule 1: All operations on the GUI thread should be short.
GUI Components are not threadsafe

The GUI components in Java’s graphics library (e.g. buttons, sliders, progress bars, dialogs, windows, scrollpanes, etc.) are not threadsafe. Updates you make on a worker thread will conflict with those made on the event handling thread. It doesn’t even matter if you enclose your worker code in a synchronized block—the internal component management code in Swing won't care!

Therefore,

Rule 2: Operate on Swing components only on the GUI thread.

No surprise there: this is how one deals with thread-unsafe objects anyway.

Threads in Swing

Here's a little application that shows what kind of operations are performed on which threads:

GUIThreadDemo.java
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.ImageObserver;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class GUIThreadDemo {

    public static void main(String[] args) {
        JFrame frame = new JFrame("Click in the window to exit");
        frame.setSize(600, 240);
        frame.setVisible(true);

        frame.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                System.out.println("MouseEvent handled in " + Thread.currentThread());
                System.exit(0);
            }
        });

        System.out.println("main() runs in " + Thread.currentThread());

        Thread.startVirtualThread(() ->
            System.out.println("Custom thread is " + Thread.currentThread())
        );

        //  SwingUtilities with lambda
        SwingUtilities.invokeLater(() ->
            System.out.println("SwingUtilities.invokeLater() runs on " + Thread.currentThread())
        );

        Image image = Toolkit.getDefaultToolkit()
                .getImage(GUIThreadDemo.class.getClassLoader().getResource("face.gif"));

        frame.getContentPane().getGraphics().drawImage(image, 0, 0,
            (img, flags, x, y, width, height) -> {
                if ((flags & ImageObserver.ALLBITS) != 0) {
                    System.out.println("Image loaded in " + Thread.currentThread());
                    return false;
                }
                return true;
            }
        );
    }
}

On my box it outputs:

main() runs in Thread[#3,main,5,main]
Custom thread is VirtualThread[#37]/runnable@ForkJoinPool-1-worker-1
SwingUtilities.invokeLater() runs on Thread[#35,AWT-EventQueue-0,6,main]
Image loaded in Thread[#40,Image Fetcher 0,3,main]
MouseEvent handled in Thread[#35,AWT-EventQueue-0,6,main]

How to Screw Up

Here are two scenarios that are bad:

Thread-Related Methods in SwingUtilities

If you find yourself writing some serious GUI code, be aware of these three methods from the SwingUtilities class:

All these methods do is call the same-named methods in EventQueue. The implementation of these methods are interesting, and worth a look:

package awt;
public class EventQueue {
    .
    .
    .
    public static void invokeLater(Runnable runnable) {
        Toolkit.getEventQueue().postEvent(
            new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
    }

    public static void invokeAndWait(Runnable runnable)
             throws InterruptedException, InvocationTargetException {
        if (EventQueue.isDispatchThread()) {
            throw new Error("Cannot call invokeAndWait from the event dispatcher thread");
        }
        class AWTInvocationLock {}
        Object lock = new AWTInvocationLock();
        InvocationEvent event =
            new InvocationEvent(Toolkit.getDefaultToolkit(), runnable, lock, true);
        synchronized (lock) {
            Toolkit.getEventQueue().postEvent(event);
            lock.wait();
        }
        Throwable eventThrowable = event.getThrowable();
        if (eventThrowable != null) {
            throw new InvocationTargetException(eventThrowable);
        }
    }

    public static boolean isDispatchThread() {
        EventQueue eq = Toolkit.getEventQueue();
        EventQueue next = eq.nextQueue;
        while (next != null) {
            eq = next;
            next = eq.nextQueue;
        }
        return (Thread.currentThread() == eq.dispatchThread);
    }
}

As you can probably imagine, the InvocationEvent looks like:

package java.awt.event;
public class InvocationEvent extends AWTEvent implements ActiveEvent {
    public InvocationEvent(Object source, Runnable runnable) {
        ....
    }
    public InvocationEvent(
            Object source, Runnable runnable, Object notifier,
            boolean catchThrowables) {
        ....
    }
    public void dispatch() {
        if (catchExceptions) {
            try {
                runnable.run();
            }
            catch (Throwable t) {
                if (t instanceof Exception) {
                    exception = (Exception) t;
                }
                throwable = t;
            }
        }
        else {
            runnable.run();
        }
        if (notifier != null) {
            synchronized (notifier) {
                notifier.notifyAll();
            }
        }
    }
    public Exception getException() {...}
    public Throwable getThrowable() {...}
    .
    .
    .
}

SwingWorker

The SwingWorker class gives you a mechanism for performing time-consuming tasks on a worker thread, then updating the UI after the task completes.

It’s best to read the Javadocs for a deeper explanation and some examples.

Threads and I/O

TODO

Scheduling

Threads are important (actually, required) when you need to run certain jobs at certain times, or periodically, or until certain times.

Understand the difference between the different uses of scheduling:

Priority-Based Scheduling

Every Java thread has a priority between 0 and 10. The priority is initially that of the thread that created it. You can call setPriority() to change it; the JVM will never change it on its own.

The JVM uses a preemptive, priority-based scheduler. When it is time to pick a thread to run, the scheduler picks

the highest priority thread that is currently runnable

and

when a thread gets moved into the runnable state and the currently executing thread as strictly lower priority, the higher priority thread preempts the running thread

and

among threads of the same priority, round-robin scheduling is used

These are only guidelines, or hints, provided to the operating systems. The operating system makes the ultimate scheduling choices. In other words, the priority within the JVM is not the same as the priority of the thread assigned by the operating system. Don’t even expect it to be. The O.S. can give boosts; the JVM will not. So you can never rely on priorities for synchronization, ordering of operations, or race condition prevention.

Scheduling Events

Here are events that cause the scheduler to take action:

An example of how things might work:

priority.png

Things to consider

Generally you should not care about this stuff, but if you have a platform that you know is not timesliced and you have a CPU-intensive thread, you might want to give this thread a priority less than the GUI thread. Do you see why?

And in general, threads that block a lot should get a higher priority than those that are CPU-intensive. Do you see why?

Thread Pools

A thread pool is a fixed collection of threads that you submit tasks to. A thread in the pool generally wakes up to run a task, then goes back to idling.

By task we mean either a Runnable or a Callable.

package java.lang;
public class Runnable {
    void run();
}
package java.util.concurrent;
public interface Callable<V> {
    V call() throws Exception;
}

What good are thread pools?

But don’t use thread pools when:

Executors

An executor is something that runs a task:

package java.util.concurrent;
public interface Executor {
    void execute(Runnable command);
}

There’s a simple executor interface, called ExecutorService:

package java.util.concurrent;
public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future< submit(Runnable task);

    <T> List<Future<T>> invokeAll(Collection<Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<Callable<T>> tasks, long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> T invokeAny(Collection<Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<Callable<T>> tasks, long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

There is an executor which extends the simple executor service with the ability to schedule tasks:

package java.util.concurrent;
public interface ScheduledExecutorService extends ExecutorService {
    ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
    <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
    ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
    ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
}

Here is an example showing a simple thread pool in action. The example is very simple; it only calls execute, not submit, and does not care about what results the tasks are computing (there are none), nor does it care about termination.

ThreadPoolBouncer.java
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

/**
 * A simple Swing application that serves as a demostration of graphics and
 * thread pools. A user can launch bouncing balls by clicking on a button. Each
 * ball is run on a thread and these threads come from a fixed-size thread pool.
 * Each ball lives for a limited but not too short of a time, so repeatedly
 * clicking the launch button can cause the pool to fill up and require new
 * balls to wait.
 *
 * The main frame window includes a status line with information about the
 * application's state.
 */
public class ThreadPoolBouncer extends JFrame implements Runnable {
    private Random random = new Random();

    private static final int POOL_SIZE = 8;
    private JPanel canvas = new JPanel();
    private JLabel statusLabel = new JLabel();
    private int activeCount = 0;
    private int waitCount = 0;

    /**
     * A thread to do the rendering on, since we don't want to render on the user
     * interface thread.
     */
    private Thread renderThread = Thread.ofVirtual().unstarted(this::run);

    /**
     * Stores all the ball tasks. Note balls are not threads -- they are runnables
     * which will be run on threads from a thread pool.
     */
    private List<Ball> balls = Collections.synchronizedList(new ArrayList<Ball>());

    private ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);

    /**
     * An action to launch a new ball.
     */
    private Action launchAction = new AbstractAction("Launch") {
        public void actionPerformed(ActionEvent e) {
            Ball ball = new Ball();
            balls.add(ball);
            synchronized (this) {
                waitCount++;
                updateStatus();
            }
            pool.execute(ball);
        }
    };

    /**
     * Constructs and lays out the application frame.
     */
    public ThreadPoolBouncer() {
        JPanel statusPanel = new JPanel();
        statusPanel.add(new JButton(launchAction));
        statusPanel.add(statusLabel);
        getContentPane().add(statusPanel, BorderLayout.SOUTH);
        getContentPane().add(canvas, BorderLayout.CENTER);
        setResizable(false);
    }

    /**
     * Renders the canvas with all the balls. First it creates a back buffer and
     * graphics objects for the back buffer and the canvas. Then it loops around
     * drawing all the live balls to the back buffer then blitting the back buffer
     * to the canvas.
     */
    public void run() {
        Image buffer = createImage(canvas.getWidth(), canvas.getHeight());
        Graphics g = buffer.getGraphics();
        Graphics gc = canvas.getGraphics();
        while (true) {
            g.setColor(Color.white);
            g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
            synchronized (balls) {
                for (Ball b : balls) {
                    b.draw(g);
                }
            }
            gc.drawImage(buffer, 0, 0, canvas);
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    private void updateStatus() {
        final String text;
        synchronized (this) {
            text = "Waiting: " + waitCount + " Active: " + activeCount;
        }
        SwingUtilities.invokeLater(() -> statusLabel.setText(text));
    }

    /**
     * Runs the demonstration as an application.
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            ThreadPoolBouncer bouncer = new ThreadPoolBouncer();
            bouncer.setTitle("Bouncing Balls");
            bouncer.setSize(800, 600);  // Set the size of the window
            bouncer.setVisible(true);
            bouncer.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            bouncer.renderThread.setPriority(Thread.MAX_PRIORITY);
            bouncer.renderThread.start();
        });
    }

    /**
     * A ball is something that bounces around on a JPanel. Balls are created with a
     * random color not too close to white, initially at a random position on the
     * canvas and oriented in an random direction (neither horizontal or vertical).
     */
    private class Ball implements Runnable {
        private Color color = new Color(
            random.nextInt(200), random.nextInt(200), random.nextInt(255)
        );
        private int x = random.nextInt(getWidth());
        private int y = random.nextInt(getWidth());
        private int dx = 1 + random.nextInt(5);
        private int dy = 1 + random.nextInt(3);
        private static final int RADIUS = 10;

        public void draw(Graphics g) {
            g.setColor(color);
            g.fillOval(x, y, RADIUS, RADIUS);
        }

        protected void move() {
            x += dx;
            y += dy;
            if (x < 0) {
                x = 0;
                dx = -dx;
            }
            if (x + RADIUS >= canvas.getWidth()) {
                x = canvas.getWidth() - RADIUS;
                dx = -dx;
            }
            if (y < 0) {
                y = 0;
                dy = -dy;
            }
            if (y + RADIUS >= canvas.getHeight()) {
                y = canvas.getHeight() - RADIUS;
                dy = -dy;
            }
        }

        /**
         * A ball moves 1000 times. After each position update, it sleeps 5ms.
         */
        public void run() {
            synchronized (this) {
                waitCount--;
                activeCount++;
                updateStatus();
            }
            for (int i = 1; i <= 1000; i++) {
                move();
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            balls.remove(this);
            synchronized (this) {
                activeCount--;
                updateStatus();
            }
        }
    }
}

Here is an example creating a thread pool that executes callable tasks that we submit, and read out results at a later time from a Future object.

Summer.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Callable;

public class Summer {

    /**
     * Sums an array using 10 threads, unless the array has fewer than 100
     * elements, in which case it is summed up directly.
     */
    public static int sum(int[] a) throws InterruptedException, ExecutionException {
        if (a.length < 100) {
            return Arrays.stream(a).sum();
        }

        final var numThreads = 10;
        var sliceSize = (a.length + numThreads - 1) / numThreads;
        var tasks = new ArrayList<Callable<Integer>>(numThreads);
        for (var i = 0; i < numThreads; i++) {
            final int low = i * sliceSize;
            final int high = Math.min(low + sliceSize, a.length);
            tasks.add(() -> Arrays.stream(a, low, high).sum());
        }

        try (var executor = Executors.newFixedThreadPool(numThreads)) {
            var total = 0;
            for (var future : executor.invokeAll(tasks)) {
                total += future.get();
            }
            return total;
        }
    }

    public static void main(String[] args) throws Exception {
        assert sum(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }) == 55;
        assert sum(new int[] { 100, 200, 300, 400 }) == 1000;
        assert sum(new int[] {}) == 0;
        assert sum(IntStream.generate(() -> 7).limit(20000).toArray()) == 140000;
        assert sum(IntStream.generate(() -> 7).limit(19999).toArray()) == 139993;
        assert sum(IntStream.range(0, 10003).toArray()) == 50025003;
        System.out.println("All tests passed");
    }
}

Shutdown and Termination

An executor service can be shut down gracefully by calling the shutdown method. This will prevent new tasks from being submitted, but will allow currently running tasks to complete. The shutdown method will be automatically called if you create the executor service in a try-with-resources block.

java.util.Timer

If you have very simple scheduling needs, you can use a simple timer in place of a scheduled thread pool executor. The Timer class in java.util looks like:

java.swing.Timer

The swing timer class is even simpler and less flexible than the util timer, but it’s there to make it easy to execute actions on the GUI threads. Swing timers aren’t very generic: they only support one schedule. But if you just need something simple in a graphics application, it should be good enough.

Recall Practice

Here are some questions useful for your spaced repetition learning. Many of the answers are not found on this page. Some will have popped up in lecture. Others will require you to do your own research.

Summary

We’ve covered:

  • ...