Multithreading and Concurrency in Java

Multithreading and Concurrency in Java

What is Multithreading?

Multithreading is the ability of a CPU (or a single core in a multi-core processor) to execute multiple threads concurrently. In Java, a thread is the smallest unit of execution within a process. Multithreading allows a program to perform multiple tasks simultaneously, enhancing performance and responsiveness.

Why Use Multithreading?

Multithreading is essential for building efficient, high-performance applications. Some key advantages include:

? Improved Performance - Leverages multi-core processors for parallel execution, speeding up computation-heavy tasks.

? Responsiveness - Keeps applications interactive by running background tasks separately (e.g., GUI applications that perform background computations).

? Better Resource Utilization - Enables sharing of system resources more effectively.

? Simplified Modeling - Naturally represents real-world concurrent processes like handling multiple network requests.

Concurrency vs. Parallelism

  • Concurrency: Tasks appear to run simultaneously by switching between them (e.g., multitasking on a single-core CPU).
  • Parallelism: Tasks execute at the same time on different cores (e.g., utilizing multiple processors).

Threads vs. Processes

  • Threads: Lightweight, share memory, and are faster to create and switch.
  • Processes: Heavyweight, have separate memory spaces, and require more resources.


Creating Threads in Java

There are two main ways to create threads in Java:

1?? Extending the Thread Class

class MyThread extends Thread {
    public void run() {
        System.out.println("Thread is running");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}
        

2?? Implementing the Runnable Interface

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Thread is running");
    }
}

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}
        

? Best Practice: Using Runnable is preferable as it allows flexibility (Java supports only single inheritance, but interfaces can be implemented multiple times).


Thread Lifecycle

A thread can be in one of the following states:

?? New - Created but not started.

?? Runnable - Ready to run, waiting for CPU time.

?? Running - Actively executing its task.

?? Blocked/Waiting - Paused, waiting for a resource.

?? Terminated - Completed execution.

Thread Priorities

Threads can have priorities ranging from 1 (lowest) to 10 (highest):

thread.setPriority(Thread.MAX_PRIORITY); // 10        

Synchronization in Multithreading

?? Race Conditions

A race condition occurs when multiple threads access shared data concurrently, leading to unpredictable results.

? Synchronized Methods and Blocks

Use the synchronized keyword to ensure only one thread accesses a critical section at a time:

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }
}        

?? Intrinsic Locks (Monitor Locks)

Every Java object has an intrinsic lock (monitor). When a thread enters a synchronized block, it acquires the lock and releases it upon exit.


Inter-Thread Communication

Java provides wait(), notify(), and notifyAll() for inter-thread communication:

wait(): Releases the lock and waits for notification.

notify(): Wakes up a single waiting thread.

notifyAll(): Wakes up all waiting threads.

class Buffer {
    private int data;
    private boolean available = false;

    public synchronized void produce(int value) throws InterruptedException {
        while (available) wait();
        data = value;
        available = true;
        notifyAll();
    }

    public synchronized int consume() throws InterruptedException {
        while (!available) wait();
        available = false;
        notifyAll();
        return data;
    }
}        

Java Concurrency Utilities

1?? ExecutorService and Thread Pools

The ExecutorService framework simplifies thread management:

ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> System.out.println("Task executed"));
executor.shutdown();        

2?? Callable and Future

Callable is a task that returns a result, and Future represents the result of an asynchronous computation:

Callable<Integer> task = () -> 42;
Future<Integer> future = executor.submit(task);
System.out.println(future.get()); // 42        

CountDownLatch: Waits for a set of tasks to complete.

CyclicBarrier: Synchronizes threads at a common point.

Semaphore: Controls access to a shared resource.

3?? Locks and Synchronization Tools

? ReentrantLock : provides more flexibility than intrinsic locks:

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // Critical section
} finally {
    lock.unlock();
}        

? ReadWriteLock : Allows multiple readers or a single writer:

ReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
lock.writeLock().lock();        

? StampedLock (Optimistic Locking): An advanced lock that supports optimistic locking:

StampedLock lock = new StampedLock();
long stamp = lock.tryOptimisticRead();        

? Thread Safety (Immutable Objects): Immutable objects are inherently thread-safe:

public final class ImmutableClass {
    private final int value;

    public ImmutableClass(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}        

? Thread-Local Variables: Each thread has its own copy of a thread-local variable

ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);        

? Atomic Variables: provide thread-safe operations without locks

public final class ImmutableClass {
    private final int value;

    public ImmutableClass(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}        

Concurrent Collections

1?? ConcurrentHashMap

A thread-safe version of HashMap:

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 42);        

2?? CopyOnWriteArrayList

A thread-safe version of ArrayList:

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("value");        

3?? BlockingQueue

A thread-safe queue that supports blocking operations

BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("value");
String value = queue.take();        

Fork/Join Framework

?? ForkJoinPool

A thread pool for parallel execution:

ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new MyTask());        
class MyTask extends RecursiveTask<Integer> {
    protected Integer compute() {
        return 42;
    }
}
        

?? RecursiveTask

Tasks that can be split into smaller subtasks

class MyTask extends RecursiveTask<Integer> {
    protected Integer compute() {
        return 42;
    }
}        

Common Concurrency Issues

? Deadlocks - Occurs when two threads hold locks that the other needs.

? Livelocks - Threads actively change state but fail to make progress.

? Starvation - A thread is unable to gain access to a resource due to high-priority threads monopolizing it.


?? Real-World Applications of Multithreading

? Web Servers - Handle multiple client requests concurrently.

? Big Data Processing - Process large datasets in parallel.

? Real-Time Systems - Ensure timely execution of critical tasks.

? Gaming - Manages physics, rendering, and AI in separate threads.


?? This article provides a comprehensive overview of Multithreading and Concurrency in Java with theoretical concepts, practical examples, and best practices.

?? What are your experiences with multithreading in Java? Let’s discuss in the comments!

If you found this article helpful, feel free to share it with your network. For more in-depth tutorials on Java and other programming topics, follow me on LinkedIn.

要查看或添加评论,请登录

Eugene Koshy的更多文章