0% found this document useful (0 votes)
11 views

Java Thread Synchronization Detailed Slides

Uploaded by

aadit shah
Copyright
© © All Rights Reserved
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views

Java Thread Synchronization Detailed Slides

Uploaded by

aadit shah
Copyright
© © All Rights Reserved
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 15

Synchronizing Threads in Java

Understanding synchronization
techniques with examples and code
solutions
Introduction to Thread Synchronization
• Objective: Describe the importance of thread
communication and the issues that arise with
shared resources.

• Explanation: Threads often communicate via


shared data structures in the main thread's
heap, leading to synchronization problems,
such as data inconsistency and unexpected
results.
Example Scenario - Bank Account
• Description: The Bank Account scenario helps illustrate
synchronization problems. A bank account allows deposits and
withdrawals, but concurrent access by multiple threads can lead
to data inconsistency.
class BankAccount {
private double balance;

public BankAccount(double bal) { balance = bal; }


public BankAccount() { this(0); }

public double getBalance() { return balance; }

public void deposit(double amt) { ... }


public void withdraw(double amt) { ... }
}
Deposit and Withdraw Methods
• The deposit and withdraw methods perform simple arithmetic on the
balance. If a thread is interrupted mid-operation, data inconsistency can
occur.

public void deposit(double amt) {


double temp = balance;
temp += amt;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
System.err.println(e.getMessage());
}
System.out.println("after deposit balance = $" +
temp);
balance = temp;
}
public void withdraw(double amt) {
if (balance < amt) {
System.out.println("Insufficient funds!");
return;
}
double temp = balance;
temp -= amt;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
System.err.println(e.getMessage());
}
System.out.println("after withdrawal balance = $" + temp);
balance = temp;
}
Producer and Consumer Threads
class Producer extends Thread {
private BankAccount account;

public Producer(BankAccount acct) { account = acct; }

public void run() {


for (int i = 0; i < 5; i++) {
account.deposit(10);
}
}
}
Producer and Consumer Threads
class Consumer extends Thread {
private BankAccount account;

public Consumer(BankAccount acct) { account = acct; }

public void run() {


for (int i = 0; i < 5; i++) {
account.withdraw(10);
}
}
}
Producer and Consumer Threads
Imagine two threads running deposit() and withdraw() simultaneously:

1. Producer starts depositing $10.


• Reads balance: $100
2. Consumer starts withdrawing $10 before Producer updates.
• Reads balance: $100
• Withdraws $10, updates balance to $90
3. Producer resumes and updates balance to $110 (using the stale value it initially
read as $100).

The final balance should have been $90, but because of the race condition, it's
incorrectly updated to $110.

Result
In a multi-threaded environment, without synchronization:
• Producers and Consumers may not correctly see each other’s changes.
• The final balance will vary unpredictably depending on thread timing and
interruptions, which is a data race.
To prevent this, synchronization ensures that only one thread accesses or modifies
the balance at a time, leading to consistent results.
Solution 1.0 - Locks
• Using synchronized blocks allows only one thread to
access critical sections of code at a time, avoiding data
inconsistency.
class Producer extends Thread {
private BankAccount account;

public Producer(BankAccount acct) { account = acct; }

public void run() {


for (int i = 0; i < 5; i++) {
synchronized(account) { account.deposit(10); }
}
}
}
Solution 1.0 - Locks
• Using synchronized blocks allows only one thread to
access critical sections of code at a time, avoiding data
inconsistency.
class Consumer extends Thread {
private BankAccount account;

public Consumer(BankAccount acct) { account = acct; }

public void run() {


for (int i = 0; i < 5; i++) {
synchronized(account)
{ account.withdraw(10); }
}
}
}
Solution 1.1 - Locks with Notification
Conditions
• For finer control, we can use wait and notify to
ensure consumers wait for sufficient funds before
withdrawing.
class Producer extends Thread {
private BankAccount account;
public Producer(BankAccount acct) { account = acct; }
public void run() {
for (int i = 0; i < 15; i++) {
synchronized(account) {
account.deposit(10);
account.notify();
}
}
}
}
Solution 1.1 - Locks with Notification
Conditions
• For finer control, we can use wait and notify to ensure
consumers wait for sufficient funds before withdrawing.

class Consumer extends Thread {


private BankAccount account;
public Consumer(BankAccount acct) { account = acct; }
public void run() {
for (int i = 0; i < 5; i++) {
synchronized(account) {
while(account.getBalance() < 10) {
try { account.wait(); } catch
(InterruptedException e) {

System.err.println(e.getMessage()); }
}
account.withdraw(10);
Solution 2.0 - Monitors
• Java's synchronized methods (monitors) simplify code and ensure
synchronization automatically, avoiding mistakes with explicit locks.

class BankAccount {
private double balance;
public BankAccount(double bal) { balance = bal; }
public BankAccount() { this(0); }

public synchronized double getBalance() { return balance; }

public synchronized void deposit(double amt) {


double temp = balance;
temp += amt;
try { Thread.sleep(300); } catch (InterruptedException e) {
System.err.println(e.getMessage());
}
System.out.println("after deposit balance = $" + temp);
balance = temp;
notify();
}
Solution 2.0 - Monitors
• Java's synchronized methods (monitors) simplify code and ensure synchronization
automatically, avoiding mistakes with explicit locks.
class BankAccount {
public synchronized void withdraw(double amt) {
while (balance < amt) {
try {
wait();
} catch (InterruptedException e) {
System.err.println(e.getMessage());
}
}
double temp = balance;
temp -= amt;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
System.err.println(e.getMessage());
}
System.out.println("after withdrawal balance = $" + temp);
balance = temp;
}}
Conclusion
• Summary of Techniques: Locks, wait/notify,
and Monitors help in synchronizing threads
effectively in Java.

• Best Practice: Using synchronized methods


(monitors) is generally safer and simplifies
code, reducing the risk of synchronization
issues.

You might also like