1. Key Concepts
- Thread: A lightweight process with its own execution path.
- Concurrency: Multiple tasks making progress within the same time frame.
- Parallelism: Tasks running simultaneously on multiple processors.
2. Thread Creation
Using Thread
class
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // Start the thread
}
}
Using Runnable
interface
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable is running");
}
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start(); // Start the thread
}
}
3. Thread Lifecycle
- New: Thread is created but not yet started.
- Runnable: Thread is ready but not yet executed.
- Blocked: Thread is waiting for a resource.
- Waiting: Thread is waiting for another thread.
- Timed Waiting: Thread is waiting for a specific period.
- Terminated: Thread has completed its execution.
4. Thread Methods
start()
: Starts the thread.run()
: Code executed by the thread.sleep(long millis)
: Pauses the thread for a given time.yield()
: Gives up CPU time to allow other threads to execute.join()
: Waits for the thread to finish before proceeding.interrupt()
: Interrupts the thread.getName()
: Gets the thread’s name.setPriority(int priority)
: Sets the thread’s priority.
5. Thread Synchronization
Using synchronized
keyword
- Method-level synchronization:
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
- Block-level synchronization:
class Counter {
private int count = 0;
public void increment() {
synchronized(this) {
count++;
}
}
public int getCount() {
return count;
}
}
Using ReentrantLock
import java.util.concurrent.locks.*;
class Counter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
6. Deadlock Prevention
Deadlock occurs when two or more threads are blocked forever, waiting for each other to release resources.
Preventing Deadlock:
- Avoid circular dependencies.
- Use timeouts with
ReentrantLock
.
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
// Perform work
} else {
lock1.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
7. Executor Framework
Manages a pool of threads for efficient task execution.
Using ExecutorService
import java.util.concurrent.*;
public class ExecutorExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> System.out.println("Task 1"));
executor.submit(() -> System.out.println("Task 2"));
executor.shutdown(); // Initiates shutdown
}
}
Common Executors:
Executors.newFixedThreadPool(int nThreads)
Executors.newCachedThreadPool()
Executors.newSingleThreadExecutor()
8. Callable and Future
Callable
: Returns a result or throws an exception.Future
: Represents the result of an asynchronous computation.
import java.util.concurrent.*;
public class CallableExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
Callable<Integer> task = () -> 5 + 3;
Future<Integer> future = executor.submit(task);
System.out.println("Result: " + future.get()); // Output: 8
executor.shutdown();
}
}
9. Concurrent Collections
Java provides thread-safe collections in java.util.concurrent
:
CopyOnWriteArrayList
ConcurrentHashMap
BlockingQueue
ConcurrentSkipListMap
Example with ConcurrentHashMap
:
import java.util.concurrent.*;
public class ConcurrentMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
System.out.println(map.get("key1")); // Output: value1
}
}
10. Thread Communication
wait()
: Makes the current thread wait until another thread signals.notify()
: Wakes up a waiting thread.notifyAll()
: Wakes up all waiting threads.
class Counter {
private int count = 0;
public synchronized void increment() throws InterruptedException {
while (count == 1) {
wait(); // Wait for other threads
}
count++;
notify(); // Notify other threads
}
public synchronized void decrement() throws InterruptedException {
while (count == 0) {
wait();
}
count--;
notify();
}
}
11. Concurrency Utilities
CountDownLatch
- Used to make one or more threads wait until a set of operations completes.
import java.util.concurrent.*;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
Thread t1 = new Thread(() -> {
System.out.println("Task 1 is working");
latch.countDown();
});
Thread t2 = new Thread(() -> {
System.out.println("Task 2 is working");
latch.countDown();
});
t1.start();
t2.start();
latch.await(); // Main thread waits
System.out.println("All tasks are finished.");
}
}
CyclicBarrier
- Makes threads wait for each other to reach a common barrier point.
import java.util.concurrent.*;
public class CyclicBarrierExample {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(2, () -> System.out.println("Both threads have reached the barrier"));
Thread t1 = new Thread(() -> {
System.out.println("Thread 1 reached the barrier");
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
System.out.println("Thread 2 reached the barrier");
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
}
}
Semaphore
- Controls access to a shared resource with a set number of permits.
import java.util.concurrent.*;
public class SemaphoreExample {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(2); // Allow 2 threads
Runnable task = () -> {
try {
semaphore.acquire(); // Acquire permit
System.out.println(Thread.currentThread().getName() + " acquired a permit");
Thread.sleep(2000); // Simulate work
System.out.println(Thread.currentThread().getName() + " releasing a permit");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // Release permit
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
Thread t3 = new Thread(task);
t1.start();
t2.start();
t3.start();
}
}
Exchanger
- Allows two threads to exchange objects.
import java.util.concurrent.*;
public class ExchangerExample {
public static void main(String[] args) throws InterruptedException {
Exchanger<String> exchanger = new Exchanger<>();
Thread t1 = new Thread(() -> {
try {
String data = "Data from t1";
System.out.println("t1 exchanging: " + data);
data = exchanger.exchange(data);
System.out.println("t1 received: " + data);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
String data = "Data from t2";
System.out.println("t2 exchanging: " + data);
data = exchanger.exchange(data);
System.out.println("t2 received: " + data);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
}
}
12. volatile
Keyword
volatile
guarantees visibility of the value to all threads. It doesn’t provide atomicity.
class VolatileExample {
private volatile boolean flag = false;
public void toggleFlag() {
flag = !flag;
}
public boolean isFlag() {
return flag;
}
}
public class Main {
public static void main(String[] args) {
VolatileExample example = new VolatileExample();
new Thread(() -> {
while (!example.isFlag()) {
// Wait until flag is changed
}
System.out.println("Flag was changed!");
}).start();
// Changing flag in main thread
example.toggleFlag();
}
}
- Important: Use
volatile
when you only need to ensure visibility (e.g., for flags or single variables) but not atomicity. For more complex operations, useAtomic
classes.
13. atomic
Classes
Java provides atomic classes in the java.util.concurrent.atomic
package to perform thread-safe operations on single variables.
AtomicInteger
- Supports atomic increments, decrements, and updates.
import java.util.concurrent.atomic.AtomicInteger;
class AtomicExample {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // Atomically increment the value
}
public int getCount() {
return count.get();
}
}
public class Main {
public static void main(String[] args) {
AtomicExample example = new AtomicExample();
example.increment();
example.increment();
System.out.println("Count: " + example.getCount()); // Output: Count: 2
}
}
AtomicBoolean
- Atomic operation on
boolean
values.
import java.util.concurrent.atomic.AtomicBoolean;
class AtomicBooleanExample {
private AtomicBoolean flag = new AtomicBoolean(false);
public void toggleFlag() {
flag.set(!flag.get());
}
public boolean isFlag() {
return flag.get();
}
}
public class Main {
public static void main(String[] args) {
AtomicBooleanExample example = new AtomicBooleanExample();
example.toggleFlag();
System.out.println("Flag: " + example.isFlag()); // Output: Flag: true
}
}
14. Best Practices
- Use thread pools instead of manually managing threads.
- Minimize synchronization to avoid performance bottlenecks.
- Use timeout mechanisms to prevent deadlocks.
- Use volatile for variables shared between threads that don’t require atomicity.
- Use atomic classes for thread-safe operations on single variables.
- Properly release resources (e.g.,
ReentrantLock.unlock()
,finally
blocks). - Prefer higher-level concurrency utilities for complex scenarios.