Threads and Concurrency

I am a passionate software engineer in training at ALX. I have a strong preference for back-end development. I am on course to share my learning journey with you.
Introduction
In the previous lesson, we explored File Handling in Java, which allows reading and writing data efficiently. Now, we move on to Threads and Concurrency, which enables multitasking and efficient CPU utilization in Java programs.
Creating Threads using Runnable & Thread Class
Java provides two ways to create threads:
By implementing the
RunnableinterfaceBy extending the
Threadclass
Using Runnable Interface
class MyRunnable implements Runnable {
public void run() {
System.out.println("Thread running: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
/**
* Output
* Thread running: Thread-0
*/
Extending the Thread Class
class MyThread extends Thread {
public void run() {
System.out.println("Thread running: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
/**
* Output
* Thread running: Thread-0
*/
Synchronization Basics
When multiple threads access shared resources, data inconsistency can occur. Synchronization ensures that only one thread accesses the shared resource at a time.
Example Without Synchronization (Data Corruption)
class Counter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
class TestConcurrency {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) counter.increment();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) counter.increment();
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + counter.getCount());
}
}
From the above code, multiple threads modify the count variable at the same time. This can cause data corruption, because count++ is not atomic (it breaks down to: read → increment → write). Each thread calls the increment() 1000 times without synchronization, race conditions may occur.
Example With Synchronization (Thread-Safe)
class SynchronizedCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
class TestSynchronization {
public static void main(String[] args) throws InterruptedException {
SynchronizedCounter counter = new SynchronizedCounter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) counter.increment();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) counter.increment();
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + counter.getCount());
}
}
In this version, increment() is marked as synchronized, ensuring that only one thread can access it at a time. This guarantees thread safety, even when multiple threads update the same variable. So even if t1 and t2 run simultaneously, the updates to count are done one at a time, safely.
Best Practices for Thread Safety
Use
synchronizedblocks or methods to prevent data corruption.Prefer using
ReentrantLockover synchronized for more flexibility.Use
volatilekeyword for visibility of shared variables.Leverage concurrent utilities like
ExecutorServicefor thread management.
Concurrency
Concurrency is the ability of a program to run multiple tasks at the same time, often by interleaving execution across threads. In Java, concurrency is essential for:
Making applications faster by utilizing multiple CPU cores.
Keeping Graphical User Interfaces responsive.
Managing multiple users or tasks in parallel (like in web servers).
Common Concurrency Problems
Race Conditions: Two or more threads access shared data and modify it simultaneously, causing unpredictable results.
Data Corruption: This happens when shared variables are updated without proper synchronization.
Deadlock: Two threads wait on each other to release a lock, resulting in a standstill.
Tools to Manage Concurrency in Java
Use the
synchronizedkeyword which prevents multiple threads from accessing a method or block at the same time.public synchronized void increment() { count++; }
Locks (
ReentrantLock). It is more flexible thansynchronized, with try-locks, timeouts, etc.Lock lock = new ReentrantLock(); lock.lock(); try { // critical section } finally { lock.unlock(); }Volatile variables ensures changes to a variable are visible across threads.
private volatile boolean running = true;Atomic Variables (from
java.util.concurrent.atomic) for thread-safe operations without locking:AtomicInteger count = new AtomicInteger(0); count.incrementAndGet();Thread Pools & Executors manage multiple threads efficiently with:
ExecutorService executor = Executors.newFixedThreadPool(4); executor.submit(() -> doWork());
Conclusion
Threads and concurrency help in executing multiple tasks simultaneously, improving performance. Understanding how to create threads using the Runnable interface and Thread class, along with implementing synchronization, ensures efficient and thread-safe Java applications.
Stay tuned for the next topic in the Java Short Notes series!



