[go: up one dir, main page]

0% found this document useful (0 votes)
50 views26 pages

Java Concurrency 101 1709730229

Uploaded by

umangsales0
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
50 views26 pages

Java Concurrency 101 1709730229

Uploaded by

umangsales0
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 26

Java Concurrency 101

Learn the basics in minutes


Concurrency
“Concurrency is the ability of different parts or
units of a program, algorithm, or problem to be
executed out-of-order or in partial order, without
affecting the outcome. This allows for parallel
execution of the concurrent units, which can
significantly improve overall speed of the execution
in multi-processor and multi-core systems”.
[Wikipedia]
Processes and Threads
Processes are self-contained execution
environments with their own memory space and
resources.

Threads, often called lightweight processes, are units


of execution within a process. Unlike processes,
threads share resources such as memory and files,
enabling efficient, but sometimes problematic,
communication.

Java leverages multithreaded execution, where each


application typically starts with a main thread.
Additional threads can be created to perform
asynchronous tasks using the Thread class.
Creating and Managing Threads
There are two main approaches to using Thread
objects in Java:
- Direct Approach
Create a new Thread instance whenever you
need to start an asynchronous task. This gives
you direct control over thread creation and
management.
- Executor Approach
Instead of managing threads directly, pass
your tasks to an executor. The executor
handles thread management separately from
your main application logic.
In the next slides, we’ll talk about creating and
managing thread directly, while Executors will be
discussed later
Creating a thread by extending the
Thread class
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}

class MyThread extends Thread {


@Override
public void run() {
System.out.println("Thread is running ...");
}
}
Creating a thread by implementing the
Runnable interface
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(
new MyRunnable());
thread.start();
}
}

class MyRunnable implements Runnable {


@Override
public void run() {
System.out.println("Runnable is running ...");
}
}
Creating a thread by utilizing lambda
expressions with Runnable
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println(
"Thread with lambda is running ...");
});
thread.start();
}
}
Guarded Blocks
Guarded blocks serve as a synchronization mechanism in
concurrent programming, allowing threads to coordinate
their execution based on specific conditions or predicates.
Alongside the Object.wait(), Object.notify(), and
Object.notifyAll() methods, they facilitate
synchronization by enabling threads to wait within the
blocks until the conditions are met.
Here's an overview of their functionality:
1. Threads enter guarded blocks, often within a
synchronized context;
2. Within the block, threads check a condition;
3. If the condition isn't met, threads invoke wait() and
relinquish the monitor;
4. When the condition changes, another thread calls
notify() or notifyAll() to wake up waiting threads;
5. The awakened threads reacquire the monitor and
recheck the condition before proceeding.
Thread States
- NEW
A thread that has been created but not yet
started;
- RUNNABLE
A thread executing in the Java virtual machine;
- BLOCKED
A thread that is blocked waiting for a monitor lock;
- WAITING
A thread that is waiting indefinitely for another
thread to perform a particular action;
- TIMED_WAITING
A thread that is waiting for another thread to
perform an action for up to a specified waiting
time;
- TERMINATED
A thread that has finished execution.
Thread Methods
- start()
It starts a Thread;
- sleep()
It allows a thread to pause its execution for a
specified period of time;
- interrupt()
It interrupts a thread's execution by setting its
interrupt status flag;
- join()
It allows one thread to wait for the completion of
another thread before proceeding.
Multi-threading Challenges
Race Conditions: Occur when multiple threads access shared resources
without proper synchronization, leading to unpredictable behavior or data
corruption.

Deadlocks: Threads are blocked indefinitely, waiting for each other to


release resources, often caused by acquiring multiple locks in different
orders.

Starvation: Threads are perpetually denied access to resources due to


others monopolizing them, commonly seen with higher-priority threads.

Livelocks: Threads are actively resolving resource conflicts but unable to


make progress, resembling deadlocks but with continuous activity.

Thread Interference: Multiple threads accessing shared mutable state


without synchronization, resulting in unexpected behavior or lost updates.

Memory Consistency Errors: Different threads have inconsistent views of


shared memory due to lack of synchronization, leading to unexpected
results.

Performance Overhead: Context switching, synchronization, and thread


coordination introduce overhead, and improper use can degrade
performance.
Volatile Keyword
The volatile keyword ensures that any thread accessing
a volatile variable reads its most recent write value.
Unlike regular variables, whose values may be cached by
each thread, volatile variables are always read directly
from the main memory.

public class Counter {


private volatile boolean flag;
public void enableFlag() { flag = true; }
public void disableFlag() { flag = false; }
public boolean getFlag() { return flag; }
}
The volatile keyword does not ensure atomicity.
Therefore, it should be used judiciously and alongside
other synchronization mechanisms as needed.
Synchronization
Synchronization is a technique used to control
access to shared resources in a multithreaded
environment. When multiple threads access shared
data concurrently, synchronization ensures that
only one thread can execute a synchronized block of
code or method at a time.
Synchronization using Synchronized
Methods
One way to synchronize code in Java involves
marking the entire method with the synchronized
keyword.

public class Counter {


private int count;
public synchronized void inc() {
// more code
count++;
}
public int getCount() { return count; }
}
Synchronization using Synchronized
Blocks
Using synchronized blocks is preferred over synchronized
methods because it applies synchronization only to the
critical section of the code.

public class Counter {


private int count;
public void inc() {
// more code
synchronized(this) {
count++;
}
}
public int getCount() { return count; }
}
Synchronization using Lock Objects
Using explicit lock objects provide more control and flexibility
compared to synchronized blocks.

public class Counter {


private int count;
private Lock lock = new ReentrantLock();
public void inc() {
// more code
lock.lock();
try { count++; }
finally { lock.unlock(); }
}
public int getCount() { return count; }
}
Synchronization: Using Atomic
Variables
Using atomic variables ensures atomicity without the
need for explicit synchronization, simplifying the code.

public class Counter {


private AtomicInteger count =
new AtomicInteger(0);
public void inc() {
// more code
count.incrementAndGet();
}
public int getCount() { return count.get(); }
}
High-Level Concurrency
High-level concurrency encompasses advanced
concurrency utilities and abstractions provided by
the Java Concurrency API.
These constructs encapsulate common concurrency
patterns and offer expressive and efficient ways to
manage threads, coordinate tasks, and access
shared resources.
We will talk about:
- Executors and Thread Pools
- Concurrent Collections
- Fork/Join
- Concurrent Random Numbers
Executors and Thread Pools
Executors and thread pools provide a convenient and efficient way to
manage the execution of multiple tasks concurrently.
Executors are higher-level abstractions that decouple task submission
from execution, while thread pools consist of a collection of pre-allocated
threads ready to execute tasks.

public class Main {


public static void main(String[] args) throws Exception {
ExecutorService executor =
Executors.newFixedThreadPool(3);
executor.submit(new Task());
executor.shutdown();
}
}
class Task implements Runnable {
@Override
public void run() { /* code */ }
}
Concurrent Collections
Concurrent collections offer thread-safe
alternatives to standard collections, enabling secure
access and modification by multiple threads
simultaneously.
These collections employ efficient synchronization
mechanisms to ensure thread safety without
compromising performance.
They are grouped according to the collection
interfaces they provide, including BlockingQueue,
ConcurrentMap, and ConcurrentNavigableMap.
Fork/Join
The Fork/Join framework is a high-level
concurrency mechanism for parallelizing recursive,
divide-and-conquer algorithms.
It enables efficient parallel processing of tasks by
recursively splitting them into smaller subtasks and
executing them concurrently on multiple threads.
The Fork/Join framework is particularly useful for
exploiting multi-core processors and achieving
performance gains in compute-intensive
applications.
Concurrent Random Numbers
The ThreadLocalRandom is ideal for generating
random numbers across multiple threads or
ForkJoinTasks. Simply call
ThreadLocalRandom.current() followed by the
desired method.

public class Main {


public static void main(String[] args) {
ThreadLocalRandom tlr =
ThreadLocalRandom.current();
System.out.println(tlr.nextInt(-10, 10));
System.out.println(tlr.nextBoolean());
System.out.println(tlr.nextDouble(0, 1));
}
}
Callable Interface
The Callable interface provides a means to perform a task
concurrently that returns a result and may throw an exception.

public class Main {


public static void main(String[] args) throws Exception {
ExecutorService executor =
Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(new MyCallable());
System.out.println(future.get());
executor.shutdown();
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception { return 10; }
}
CompletableFuture
Like Callable, CompletableFuture represents a single task
with a result. However, CompletableFuture offers extensive
methods for chaining, combining, and handling
asynchronous tasks in a non-blocking manner.

public class Main {


public static void main(String[] args) throws Exception {
CompletableFuture
.supplyAsync(new MySupplier())
.thenAccept(result -> System.out.println(result));
}
}
class MySupplier implements Supplier<Integer> {
@Override
public Integer get() { return 10; }
}
Immutable Objects
An immutable object is an object whose state
cannot be modified after it is created. It ensures
thread safety and simplifies concurrent
programming by eliminating the need for
synchronization.

To create immutable objects:

- Declare the class as final;


- Declare all fields as private and final;
- Do not provide setter methods;
- Ensure that any mutable objects referenced by
the immutable object are defensively copied or
made immutable themselves.
Share your thoughts!
You can read more about Java Concurrency
in the Medium article titled

"Mastering Java Concurrency: From


Thread Objects to High-Level
Concurrency Features"

Access the article through the link shared in


the LinkedIn post.
Thank you!

You might also like