Java Multithreading Fundamentals and Synchronization Techniques
A thread represents an execution path within a program. Multithreading enables concurrent execution of multiple threads managed by the CPU scheduler.
Thread Creation Methods
Method 1: Extending Thread Class
class WorkerThread extends Thread {
@Override
public void run() {
for (int counter = 1; counter <= 5; counter++) {
System.out.println("Worker thread output: " + counter);
}
}
}
class Application {
public static void main(String[] args) {
Thread worker = new WorkerThread();
worker.start();
for (int counter = 1; counter <= 5; counter++) {
System.out.println("Main thread output: " + counter);
}
}
}
Method 2: Implementing Runnable Interface
class TaskExecutor implements Runnable {
@Override
public void run() {
for (int counter = 1; counter <= 5; counter++) {
System.out.println("Task output: " + counter);
}
}
}
class Application {
public static void main(String[] args) {
Runnable task = new TaskExecutor();
new Thread(task).start();
for (int counter = 1; counter <= 5; counter++) {
System.out.println("Main thread output: " + counter);
}
}
}
Method 3: Using Callable and FutureTask
class ComputationTask implements Callable<String> {
private int limit;
public ComputationTask(int limit) {
this.limit = limit;
}
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 1; i <= limit; i++) {
sum += i;
}
return "Sum from 1 to " + limit + " equals: " + sum;
}
}
class Application {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ComputationTask task1 = new ComputationTask(100);
FutureTask<String> future1 = new FutureTask<>(task1);
new Thread(future1).start();
ComputationTask task2 = new ComputationTask(200);
FutureTask<String> future2 = new FutureTask<>(task2);
new Thread(future2).start();
System.out.println(future1.get());
System.out.println(future2.get());
}
}
Thread Safety Issues
Thread safety problems occur when:
- Multiple threads execute simultaneous
- Shared resources are accessed concurrently
- Modifications to shared resources occur
Bank Account Example
class BankAccount {
private String accountNumber;
private double balance;
public BankAccount(String number, double initialBalance) {
this.accountNumber = number;
this.balance = initialBalance;
}
public void withdraw(double amount) {
String customer = Thread.currentThread().getName();
if (balance >= amount) {
System.out.println(customer + " withdrew " + amount + " successfully!");
balance -= amount;
System.out.println(customer + " remaining balance: " + balance);
} else {
System.out.println(customer + " insufficient funds");
}
}
}
class WithdrawalThread extends Thread {
private BankAccount account;
public WithdrawalThread(BankAccount acc, String name) {
super(name);
this.account = acc;
}
@Override
public void run() {
account.withdraw(100000);
}
}
class BankingApplication {
public static void main(String[] args) {
BankAccount sharedAccount = new BankAccount("ICBC-110", 100000);
new WithdrawalThread(sharedAccount, "Alice").start();
new WithdrawalThread(sharedAccount, "Bob").start();
}
}
Synchronization Solutions
Synchronization ensures sequential access to shared resources.
Synchronized Block
public void withdraw(double amount) {
String customer = Thread.currentThread().getName();
synchronized (this) {
if (balance >= amount) {
System.out.println(customer + " withdrew " + amount + " successfully!");
balance -= amount;
System.out.println(customer + " remaining balance: " + balance);
} else {
System.out.println(customer + " insufficient funds");
}
}
}
Synchronized Method
public synchronized void withdraw(double amount) {
String customer = Thread.currentThread().getName();
if (balance >= amount) {
System.out.println(customer + " withdrew " + amount + " successfully!");
balance -= amount;
System.out.println(customer + " remaining balance: " + balance);
} else {
System.out.println(customer + " insufficient funds");
}
}
Lock Interface Implementation
class BankAccount {
private final Lock lock = new ReentrantLock();
private String accountNumber;
private double balance;
public void withdraw(double amount) {
String customer = Thread.currentThread().getName();
lock.lock();
try {
if (balance >= amount) {
System.out.println(customer + " withdrew " + amount + " successfully!");
balance -= amount;
System.out.println(customer + " remaining balance: " + balance);
} else {
System.out.println(customer + " insufficient funds");
}
} finally {
lock.unlock();
}
}
}
Thread Pool Management
Thread pools provide reusable thread technology to optimize resource usage.
Manual Thread Pool Creation
class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService pool = new ThreadPoolExecutor(
3, 5, 8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
Future<String> result1 = pool.submit(new ComputationTask(100));
Future<String> result2 = pool.submit(new ComputationTask(200));
try {
System.out.println(result1.get());
System.out.println(result2.get());
} catch (Exception e) {
e.printStackTrace();
} finally {
pool.shutdown();
}
}
}
Using Executors Utility
class ExecutorServiceDemo {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
// CPU-intensive: core threads = CPU cores + 1
// IO-intensive: core threads = CPU cores + 2
}
}
Concurrency Concepts and Thread Lifecycle
Processes are running programs containing concurrently executing threads.
Concurrency vs Parallelism
Concurrency involves multiple instructions alternating execution on a single CPU. Parallelism involves simultaneous execution of multiple instructions across multiple CPUs.
Thread States
Java defines six thread states in the Thread.State enumeration:
- NEW: Thread not yet started
- RUNNABLE: Thread executing in JVM
- BLOCKED: Thread waiting for monitor lock
- WAITING: Thread waiting indefinitely for another thread
- TIMED_WAITING: Thread waiting for specified time
- TERMINATED: Thread completed execution