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:
Package | Classes |
---|---|
java.lang | Thread 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.locks | Lock ReentrantLock ReadWriteLock StampedLock ReentrantReadWriteLock Condition LockSupport |
java.util.concurrent.atomic | AtomicInteger AtomicLong AtomicBoolean AtomicReference AtomicIntegerArray AtomicLongArray AtomicReferenceArray AtomicMarkableReference AtomicStampedReference AtomicIntegerFieldUpdater AtomicLongFieldUpdater AtomicReferenceFieldUpdater DoubleAccumulator LongAccumulator DoubleAdder LongAdder |
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:
task
: the object whose run()
method is run on the threadname
: the name of the thread (used mainly for logging or other diagnostics)tid
: the thread’s id (a unique, positive long generated by the system when the thread was created)threadGroup
: the group to which this thread belongsdaemon
: the thread’s daemon status. A daemon thread is one that performs services for other threads, or periodically runs some task, and is not expected to run to completion.contextClassLoader
: the classloader used by the threadpriority
: a small integer between Thread.MIN_PRIORITY and Thread.MAX_PRIORITY, inclusivestate
: the current state of the threadinterrupted
: the thread’s interruption statusthreadLocals
: a map of thread-local variables, which are variables that are local to the thread and not shared with other threadsinheritableThreadLocals
: a map of inheritable thread-local variables, which are variables that can be inherited by child threadsWhen 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.
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.
There are two kinds of threads.
Thread.ofPlatform()
builder (or by one of the old-fashioned constructors, but don’t use those).Thread.ofVirtual()
builder.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.
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.
The six thread states are:
NEW
RUNNABLE
BLOCKED
Object.wait
.WAITING
Object.wait
with no timeoutThread.join
with no timeoutLockSupport.park
TIMED_WAITING
Thread.sleep
Object.wait
with timeoutThread.join
with timeoutLockSupport.parkNanos
LockSupport.parkUntil
TERMINATED
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.
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:
Thread.ofPlatform()
or Thread.ofVirtual()
builders to create a thread, orFor 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:
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.
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
.
A thread stops running when:
run
method completes, orRuntime.exit()
is successful, or (2) all non-daemon threads have terminated.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
}
}
}
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:
/**
* 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
In general, getting actions in a multithreaded program to happen in some correct order is hard because:
Thread-1 Thread-2 1: r2 = A 3: r1 = B 2: B = 1 4: A = 2Initially
A == B == 0
, but it’s possible to get either r2 == 2
or r1 == 1
; a compiler can reorder the instructions in any thread.
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:
Thread.sleep
and Thread.yield
, do not reload or flush registersJava developers have a lot of choices for synchronizing:
t.join()
to wait for thread t to terminatesynchronized
statementsynchronized
StatementEvery 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.
Generally you have to synchronize them allIn the above example, if
add()
is synchronized andremove()
is not, they can still both be running at the same time!
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.)
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.
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
...
}
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.
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.
Implicit Locks | Explicit Locks |
---|---|
|
|
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.
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:
m.wait()
m.wait()
will (1) add the calling thread to m’s wait set, (2) unlock the monitor, (3) cause the thread to be blockedm.notify()
and this thread is chosenm.notifyAll()
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
Condition
is just an interfacewait()
and notify()
on a condition object!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.
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:
CountDownLatch
when one or more threads have to wait for
one or more things to happen.CountDownLatch
is a use-once object. The count never increases.countDown()
when the count is zero has no effect. (This means all the right things that the thread would be waiting for have already happened.)There are good examples in the Javadocs.
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:
await()
method throw a BrokenBarrierException
.There are good examples in the Javadocs.
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.
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:
tryAcquire()
without a timeout can break fairness; tryAcquire(0, TimeUnit.SECONDS)
will respect it.There are good examples in the Javadocs.
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:
compareAndSet
operation.
incrementAndGet, decrementAndGet
,
addAndGet
, getAndIncrement
, getAndDecrement
, getAndAdd
.class Sequencer {
private AtomicLong sequenceNumber = new AtomicLong(0);
public long next() { return sequenceNumber.getAndIncrement(); }
}
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 |
A thread can voluntarily
Thread.sleep
)Thread.yield
)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
park
and unpark
are paired both ways: if park
is called first a thread
blocks until another unparks it. If unpark
is called first, the next
park
call will not block.Thread
class had suspend
and resume
methods that had such terrible problems that these methods had to be deprecated. Pretend they do not even exist! Fortunately, park
and unpark
methods don't have the problems that the old methods did.park
is interruptible and has time out versions.park
can also return spuriously.park
is interrupted, InterruptedException
is not thrown, so check the interrupt status on return.The Javadocs have a good example of how to implement a FIFO mutex with the LockSupport
methods.
You should read Brian Goetz’s article on ThreadLocals. It's better than anything I could write.
run()
method?
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.
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 threadsafeThe 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.
Here's a little application that shows what kind of operations are performed on which threads:
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]
Here are two scenarios that are bad:
If you find yourself writing some serious GUI code, be aware of these three methods from the SwingUtilities
class:
invokeLater(Runnable)
: posts a runnable to the event dispatch thread (EDT) to be executed laterinvokeAndWait(Runnable)
: posts a runnable to the EDT and waits for it to finishisEventDispatchThread()
: returns true if the current thread is the EDTAll 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() {...}
.
.
.
}
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 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:
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.
Here are events that cause the scheduler to take action:
yield()
or sleep()
An example of how things might work:
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?
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:
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.
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.
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");
}
}
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.
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:
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.
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.
We’ve covered: