Cheatsheets - Java Concurrency
Cheatsheets - Java Concurrency
Java Concurrency
TABLE OF CONTENTS
Preface 1
Introduction 1
Basic Concepts 1
JMM "happens-before" Relationship 2
Threads and Runnable 3
Synchronization 5
The synchronized Keyword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
wait()/notify()/notifyAll() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
The volatile Keyword 7
The ThreadLocal Class 7
Immutable Objects 8
Deadlock, Livelock and Thread Starvation 9
Deadlock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Overcoming Deadlock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Livelock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Thread Starvation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
The java.util.concurrent Package 11
Executor & ExecutorService . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Semaphor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
CountDownLatch. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
CyclicBarrier. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Concurrent collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Atomics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Locks. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
PREFACE
PREFACE Concept Description
Data Race A data race is a specific Java Memory Model JMM defines the rules
type of race condition (JMM) and guarantees for how
where two or more threads interact with
threads concurrently memory, ensuring
access shared data, and visibility of changes
at least one of them made by one thread to
modifies the data. Data other threads.
races can result in
undefined behavior and JMM"HAPPENS-BEFORE"
"HAPPENS-BEFORE"
should be avoided.
JMM
RELATIONSHIP
RELATIONSHIP
Deadlocks Deadlocks occur when
two or more threads are The "happens-before" relationship describes the
blocked, waiting for guarantees and constraints JMM applies regarding
resources that will the order of actions and visibility of memory
never be released. changes in a multi-threaded environment. It is
Identifying and avoiding critical for establishing a consistent and predictable
deadlocks is essential in order of operations when we have multiple threads
concurrent accessing the same resources. It helps prevent
programming. issues like data races, ensures that memory changes
Atomic Operations Atomic operations are are visible to other threads when necessary, and
thread-safe operations provides a foundation for reasoning about the
that can be performed behavior of concurrent Java programs.
without interference
A "happens-before" relationship has the following
from other threads. Java
properties:
provides atomic classes
like AtomicInteger and
• Guarantee of Order: The "happens-before"
AtomicReference.
relationship establishes a guarantee that
Thread Local Storage Thread-local storage actions performed before an action "happens-
allows each thread to before" another action, will be seen by other
have its own copy of a threads in the expected order. It ensures that
variable, which is certain operations are observed as occurring
isolated from other sequentially.
threads. It’s useful for
• Program Order: Actions within a single thread,
storing thread-specific
as defined by the program order, are always
data.
considered to have a "happens-before"
Volatile Keyword The volatile keyword relationship. This means that actions within the
ensures that changes to same thread occur in the order specified by the
a variable are visible to program, as expected.
all threads. It’s used for
• Synchronization: Synchronization actions,
variables accessed by
such as acquiring and releasing locks via
multiple threads
synchronized blocks or ReentrantLocks, create
without
"happens-before" relationships. When a thread
synchronization.
releases a lock, all actions performed within
the synchronized block are guaranteed to be
visible to other threads that subsequently
acquire the same lock.
• Volatile Variable Access: Accesses to volatile The Runnable interface is a functional interface that
variables create "happens-before" represents a task or piece of code that can be
relationships. When a thread writes to a executed concurrently by a thread. It provides a
volatile variable, it guarantees that subsequent way to define the code that a thread should run
reads by other threads will see the most recent without the need to explicitly extend the Thread
write. class. Implementing the Runnable interface allows
• Transitivity: "happens-before" relationships for better separation of concerns and promotes
are transitive. If action A "happens-before" reusability of code.
action B, and action B "happens-before" action
C, then action A also "happens-before" action C.
public class MyRunnable implements
THREADSAND
ANDRUNNABLE
RUNNABLE Runnable {
THREADS public void run() {
The Thread class is a fundamental class for creating // Code to be executed by
and managing threads. It allows you to define and the thread
run concurrent tasks or processes within your for (int i = 1; i <= 5; i++)
application. Threads represent lightweight, {
independent paths of execution that can perform System.out.println
tasks concurrently, making it possible to achieve ("Thread: " + Thread.
parallelism in your programs. currentThread().getId() + " Count: "
+ i);
public class MyThread extends Thread }
{ }
public void run() {
// Code to be executed by public static void main(String[]
the thread args) {
for (int i = 1; i <= 5; i++) // Create two Runnable
{ instances
System.out.println MyRunnable runnable1 = new
("Thread: " + Thread. MyRunnable();
currentThread().getId() + " Count: " MyRunnable runnable2 = new
+ i); MyRunnable();
}
} // Create threads and
associate them with Runnable
public static void main(String[] instances
args) { Thread thread1 = new Thread
// Create two threads (runnable1);
MyThread thread1 = new Thread thread2 = new Thread
MyThread(); (runnable2);
MyThread thread2 = new
MyThread(); // Start the threads
thread1.start();
// Start the threads thread2.start();
thread1.start(); }
thread2.start(); }
• Always wait inside a loop that checks the Thread readerThread = new
condition being waited on. This addresses the Thread(() -> {
timing issue if another thread satisfies the while (!flag) {
condition before the wait begins and also // Busy-wait until
protects your code from spurious wake-ups - the flag becomes true
both our threads wait inside a loop governed }
by the isOddTurn flag. System.out.println("Flag
• Always ensure that you satisfy the waiting is true, readerThread can
condition before calling notify() / notifyAll(). proceed.");
Failing to do so will cause a notification but no });
thread will ever be able to escape its wait loop -
both our threads satisfy the isOddTurn flag for writerThread.start();
the other thread to continue.
readerThread.start();
}
THEVOLATILE
THE VOLATILEKEYWORD
KEYWORD
}
} public class
} DeadlockResolutionExample {
}); private static final Lock lock1
= new ReentrantLock();
thread1.start(); private static final Lock lock2
thread2.start(); = new ReentrantLock();
}
} public static void main(String[]
args) {
Runnable acquireLocks = ()
In this example:
-> {
• thread1 acquires lock1 and then waits for lock2.
lock1.lock();
try {
• thread2 acquires lock2 and then waits for lock1.
System.out.println
(Thread.currentThread().getName() +
Both threads are now waiting for a resource held
by the other, resulting in a deadlock. The program
": Holding lock 1...");
will hang indefinitely. try {
Thread.sleep(
OVERCOMING DEADLOCK
100);
} catch
Deadlocks can be avoided or resolved by various (InterruptedException e) {
techniques: }
System.out.println
• Use a Timeout: Set a timeout for acquiring
(Thread.currentThread().getName() +
locks. If a thread cannot acquire a lock within a
": Waiting for lock 2...");
specified time, it can release any locks it holds
and retry or abort. This functionality can be
easily implemented using ReentrantLock from
// Attempt to
the java.util.concurrent.locks package. acquire lock2 with a timeout of 500
milliseconds
• Lock Ordering: Establish a consistent order for
boolean
acquiring locks across all threads to prevent
acquiredLock2 = lock2.tryLock(500,
circular waiting as seen in the example below.
TimeUnit.MILLISECONDS);
• Resource Allocation Graph: Use algorithms if (acquiredLock2) {
like the resource allocation graph to detect and
try {
recover from deadlocks.
System.out
• Design for Deadlock Avoidance: Design your .println(Thread.currentThread().getN
multi-threaded code to minimize the potential ame() + ": Acquired lock 2.");
for deadlocks, such as using higher-level } finally {
abstractions like the java.util.concurrent
lock2.
classes.
unlock();
}
import } else {
java.util.concurrent.TimeUnit; System.out
import .println(Thread.currentThread().getN
java.util.concurrent.locks.Lock; ame() + ": Timeout while waiting for
import lock 2.");
java.util.concurrent.locks.Reentrant }
Lock; } finally {
lock1.unlock();
Available task types are shown in the table below. public static void main(String[]
args) {
Task Type Description
// Create an ExecutorService
Runnable Tasks Runnable tasks are using a fixed-size thread pool with
simple, non-returning 2 threads.
tasks that implement the ExecutorService
Runnable interface and executorService = Executors
perform actions without
.newFixedThreadPool(2);
producing a result.
allows a set of threads to wait for each other to Concurrent Collection Description
reach a common point before continuing execution. Class
It’s commonly used to synchronize multiple threads
BlockingQueue Blocking queues are
that perform different subtasks and need to wait
(LinkedBlockingQueue, thread-safe, bounded or
for each other before proceeding. CyclicBarrier is
DelayQueue, unbounded queues that
initialized with a count, the number of threads to
PriorityBlockingQueue, support blocking
wait before continuing, and a function called when
SynchronousQueue) operations for producer-
the count is reached and threads are allowed to
consumer scenarios. In
continue. Threads may call await() to wait for the
DelayQueue elements are
count to reach the designated number before
removed based on their
allowed to proceed operations.
delay, in
PriorityBlockingQueue
CONCURRENT COLLECTIONS based on a Comparator
and in SynchronousQueue
These concurrent collection classes provide thread-
an element is removed
safe data structures for various use cases, allowing
only when a new one
multiple threads to access and modify data
has arrived.
concurrently while ensuring data consistency and
minimizing contention. The choice of which class to ConcurrentLinkedQueue A thread-safe, non-
use depends on the specific needs of your blocking, and
concurrent application. unbounded queue based
on a linked node
Concurrent Collection Description structure, suitable for
Class high-concurrency
producer-consumer
ConcurrentHashMap A highly concurrent,
scenarios.
thread-safe
implementation of the ConcurrentLinkedDeque A thread-safe, non-
Map interface, designed blocking, double-ended
for efficient read and queue that supports
write operations in concurrent access and
multithreaded modifications from both
environments. ends.
ATOMICS System.out.println
("Incremented value: " +
The java.util.concurrent.atomic package provides incrementedValue);
classes that support atomic operations on single
variables. These classes are designed to be used in
// Add a specific value
multi-threaded applications to ensure that
atomically
operations on shared variables are performed
atomically without the need for explicit
int addedValue = atomicInt
synchronization. This helps avoid data races and .addAndGet(5);
ensures thread safety. System.out.println("Added
value: " + addedValue);
Common Atomic Classes:
// Compare and set the value
• AtomicInteger: An integer value that can be
atomically
atomically incremented, decremented, or
boolean updated = atomicInt
updated.
.compareAndSet(10, 15);
• AtomicLong: A long value that supports atomic System.out.println("Value
operations. updated? " + updated);
• AtomicBoolean: A boolean value with atomic
operations for setting and getting. // Get the current value
• AtomicReference: A generic reference type that int currentValue =
supports atomic updates. atomicInt.get();
System.out.println("Current
• AtomicStampedReference: A variant of
value: " + currentValue);
AtomicReference that includes a version stamp
to detect changes.
}
}
• AtomicIntegerArray, AtomicLongArray,
AtomicReferenceArray: Arrays of atomic values.
LOCKS
They are suitable for scenarios where you need to
perform operations like increment, compare-and- Locks provide more flexible and advanced locking
set, and update on variables without risking data mechanisms compared to synchronized blocks,
corruption due to concurrent access. Here’s a including features like reentrancy, fairness, and
simple example using AtomicInteger to demonstrate read-write locking. The java.util.concurrent.locks
atomic operations. package contains two interfaces, Lock and
ReadWriteLock and their implementation classes
ReentrantLock and ReentrantReadWriteLock
import
respectively.
java.util.concurrent.atomic.AtomicIn
teger; ReentrantLock is a reentrant mutual exclusion lock
with the same basic behavior as synchronized blocks
public class AtomicExample { but with additional features. It can be used to
public static void main(String[] control access to a shared resource and provides
args) { more flexibility and control over locking such as
AtomicInteger atomicInt = obtaining information about the state of the lock,
non-blocking tryLock(), and interruptible locking.
new AtomicInteger(0);
In this example, we use a ReentrantLock to protect a
critical section of code.
// Increment the atomic
integer atomically
int incrementedValue = import
atomicInt.incrementAndGet(); java.util.concurrent.locks.Reentrant
Lock;
import
public class ReentrantLockExample { java.util.concurrent.locks.ReadWrite
private static ReentrantLock Lock;
lock = new ReentrantLock(); import
java.util.concurrent.locks.Reentrant
public static void main(String[] ReadWriteLock;
args) {
Runnable task = () -> { public class ReadWriteLockExample {
lock.lock(); // Acquire private static ReadWriteLock
the lock readWriteLock = new
try { ReentrantReadWriteLock();
System.out.println private static String sharedData
("Thread " + Thread.currentThread = "Initial Data";
().getId() + " has acquired the
lock."); public static void main(String[]
// Perform some args) {
critical section operations Runnable reader = () -> {
Thread.sleep(1000); readWriteLock.
} catch readLock().lock(); // Acquire the
(InterruptedException e) { read lock
Thread. try {
currentThread().interrupt(); System.out.println
} finally { ("Reader Thread " + Thread
lock.unlock(); // .currentThread().getId() + " is
Release the lock reading: " + sharedData);
System.out.println // Reading shared
("Thread " + Thread.currentThread data
().getId() + " has released the } finally {
lock."); readWriteLock
} .readLock().unlock(); // Release the
}; read lock
}
// Create multiple threads };
to access the critical section
for (int i = 0; i < 3; i++) Runnable writer = () -> {
{ readWriteLock.
new Thread(task). writeLock().lock(); // Acquire the
start(); write lock
} try {
} sharedData = "New
} Data";
System.out.println
("Writer Thread " + Thread
ReentrantReadWriteLock provides separate locks for .currentThread().getId() + " is
reading and writing. It’s used to allow multiple
writing: " + sharedData);
threads to read a shared resource simultaneously,
// Writing to the
while ensuring that only one thread can write to
shared data
the resource at a time. Here’s an example.
} finally {
readWriteLock
.writeLock().unlock(); // Release
the write lock
}
};
JCG delivers over 1 million pages each month to more than 700K software
developers, architects and decision makers. JCG offers something for everyone,
including news, tutorials, cheat sheets, research guides, feature articles, source code
and more.
CHEATSHEET FEEDBACK
WELCOME
support@javacodegeeks.com
Copyright © 2014 Exelixis Media P.C. All rights reserved. No part of this publication may be SPONSORSHIP
reproduced, stored in a retrieval system, or transmitted, in any form or by means electronic, OPPORTUNITIES
mechanical, photocopying, or otherwise, without prior written permission of the publisher. sales@javacodegeeks.com