Synchronization
• In multithreaded applications, two or more threads
need to share access to the same data.
• If two threads have access to the same object and
each calls a method that modifies the state of the
object, the threads can step on each other’s toes.
• Depending on the order in which the data were
accessed, corrupted objects can result.
• Such a situation is often called a race condition.
• To avoid corruption of shared data by multiple threads,
we must synchronize the access
• Consider a bank with a number of accounts.
• We randomly generate transactions that move money
between these accounts.
• Each account has one thread.
• Each transaction moves a random amount of money
from the account serviced by the thread to another
random account.
• We have the class Bank with the method transfer.
• This method transfers some amount of money from
one account to another
Bank Example Synchronization
public void transfer(int from, int to, double amount)
// CAUTION: unsafe with multiple threads
{
System.out.print(Thread.currentThread());
//withdraws money “from” account
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
//deposit money to “to” account
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n",
getTotalBalance());
}
• The TransferRunnable class is designed with its
run method keeps moving money out of a fixed
bank account.
• In each iteration, the run method picks a random
target account and a random amount, calls
transfer on the bank object, and then sleeps.
• The program produces output as follows
class TransferRunnable implements Runnable
{
...
public void run()
{
try
{
int toAccount = (int) (bank.size() * Math.random());
double amount = maxAmount * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int) (DELAY * Math.random()));
}
catch(InterruptedException e) {}
}
}
• After a while, errors crept in and some amount of money was
either lost or spontaneously created.
• This problem occurs when two threads are simultaneously
trying to update an account.
• Suppose two threads simultaneously carry out the instruction
accounts[to] = accounts[to]+amount;
• The problem is that these are not atomic operations. The
instruction might be processed as follows:
– 1. Load accounts[to] into a register.
– 2. Add amount.
– 3. Move the result back to accounts[to].
• Now, suppose the first thread executes Steps 1
and 2, and then it is preempted.
• Suppose the second thread awakens and
updates the same entry in the account array.
• Then, the first thread awakens and completes
its Step 3.
• That action wipes out the modification of the
other thread.
• As a result, the total is no longer correct.
• There are two mechanisms for protecting a code
block from concurrent access.
– The synchronized keyword for this purpose,
– The ReentrantLock class.
• The synchronized keyword automatically provides
– a lock as well as
– an associated “condition,”
• which makes it powerful and convenient for most
cases that require explicit locking.
Reentrant Lock
• The basic outline for protecting a code block with a ReentrantLock is:
myLock.lock(); // a ReentrantLock object
try
{
critical section // code where we access the shared data
}
finally
{
myLock.unlock(); // make sure the lock is unlocked even if an exception is thrown
}
• This construct guarantees that only one thread at a time can enter the critical
section.
• As soon as one thread locks the lock object, no other thread can get past the lock
statement.
• When other threads call lock, they are deactivated until the first thread unlocks
the lock object
public class Bank
{
public void transfer(int from, int to, int amount)
{
bankLock.lock();
// Acquiring the lock for shared data
try
{
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
}
finally
{
//Once the shared access is over release lock by invoking unlock()
bankLock.unlock();
}
} // Allow only one thread to be active
...
private Lock bankLock = new ReentrantLock();
// ReentrantLock implements the Lock interface
}
Condition Objects
• You use a condition object to manage threads that have acquired a
lock but cannot do useful work.
• Suppose, we do not want to transfer money out of an account that
does not have the funds to cover the transfer
• It is entirely possible that the current thread will be deactivated
between the successful outcome of the test and the call to transfer.
if (bank.getBalance(from) >= amount)
// thread might be deactivated at this point
bank.transfer(from, to, amount);
• By the time the thread is running again, the account balance may have
fallen below the withdrawal amount.
• You must make sure that no other thread can modify the balance
between the test and the transfer action.
public void transfer(int from, int to, int amount)
{
bankLock.lock();
try
{
while (accounts[from] < amount)
{
// wait
...
}
// transfer funds
...
}
finally
{
bankLock.unlock();
}
}
• We wait until some other thread has added funds.
• But this thread has just gained exclusive access to the
bankLock, so no other thread has a chance to make a
deposit.
• This is where condition objects come in.
• A lock object can have one or more associated condition
objects.
• You obtain a condition object with the newCondition
method.
• It is customary to give each condition object a name that
evokes the condition that it represents.
class Bank
{
public Bank()
{
...
sufficientFunds = bankLock.newCondition();
}
...
private Condition sufficientFunds;
}
• If the transfer method finds that sufficient funds are not available, it
calls
sufficientFunds.await();
• The current thread is now deactivated and gives up the
lock.
• This lets in another thread that can, we hope, increase the
account balance.
• There is an essential difference between a thread that is
waiting to acquire a lock and a thread that has called await.
• Once a thread calls the await method, it enters a wait set
for that condition.
• The thread is not made runnable when the lock is available.
• Instead, it stays deactivated until another thread has called
the signalAll method on the same condition.
• When another thread transfers money, then it should call
sufficientFunds.signalAll();
• This call reactivates all threads that are waiting for the
condition.
• When the threads are removed from the wait set, they are
again runnable and the scheduler will eventually activate
them again.
• At that time, they will attempt to reenter the object.
• As soon as the lock is available, one of them will acquire
the lock and continue where it left off, returning from the
call to await
public void transfer(int from, int to, int amount)
{
bankLock.lock();
try
{
while (accounts[from] < amount)
sufficientFunds.await();
// transfer funds
...
sufficientFunds.signalAll();
}
finally
{
bankLock.unlock();
}
}
• Lock
– Lock() and unlock()
• Condition Lock object
– Condition object await() and signalAll()
• Synchronized keyword
Summary on Lock
• A lock protects sections of code, allowing only one thread
to execute the code at a time.
• A lock manages threads that are trying to enter a protected
code segment.
• A lock can have one or more associated condition objects.
• Each condition object manages threads that have entered a
protected code section but that cannot proceed.
• The Lock and Condition interfaces were added to Java SE
5.0 to give programmers a high degree of control over
locking.
Synchronized keyword
• Every object in Java has an intrinsic lock.
• If a method is declared with the synchronized keyword, then the object’s lock protects
the entire method.
• That is, to call the method, a thread must acquire the intrinsic object lock.
public synchronized void method()
{
method body
}
• is the equivalent of
public void method()
{
this.intrinsicLock.lock();
try
{
method body
}
finally { this.intrinsicLock.unlock(); }
}
• Now, we can simply declare the transfer method of
the Bank class as synchronized.
• The intrinsic object lock has a single associated
condition.
• The wait method adds a thread to the wait set, and
the notifyAll/notify methods unblock waiting threads.
• In other words, calling wait or notifyAll is the
equivalent of
intrinsicCondition.await();
intrinsicCondition.signalAll();
class Bank
{
public synchronized void transfer(int from, int to, int amount) throws
InterruptedException
{
while (accounts[from] < amount)
wait(); // wait on intrinsic object lock's single condition
accounts[from] -= amount;
accounts[to] += amount;
notifyAll(); // notify all threads waiting on the condition
}
public synchronized double getTotalBalance() { . . . }
private double[] accounts;
}
Synchronized Block
• There is a second mechanism for acquiring the
lock, by entering a synchronized block.
• When a thread enters a block of the form
synchronized (obj) // A synchronized block
{
critical section
}
• then it acquires the lock for obj.
public class Bank
{
public void transfer(int from, int to, int amount)
{
synchronized (lock) // an ad-hoc lock
{
accounts[from] -= amount;
accounts[to] += amount;
}
System.out.println(. . .);
}
...
private double[] accounts;
private Object lock = new Object();
}
Monitor
• Locks and conditions are powerful tools for thread
synchronization, but they are not very object oriented.
• A monitor is a class with only private fields.
• Each object of that class has an associated lock.
• All methods are locked by that lock.
• If a client calls obj.method(), then the lock for obj is automatically
acquired at the beginning of the method call and relinquished
when the method returns.
• Because all fields are private, this arrangement ensures that no
thread can access the fields while another thread manipulates
them.
• The lock can have any number of associated conditions.
• Earlier monitors had a syntax with condition
• We can simply call await accounts[from] >=
balance without using an explicit condition
variable
• A Java object differs from a monitor in three
important ways, compromising thread safety:
– Fields are not required to be private.
– Methods are not required to be synchronized.
– The intrinsic lock is available to clients.
Volatile
• The volatile keyword offers a lock-free mechanism for
synchronizing access to an instance field.
• If you declare a field as volatile, then the compiler and the
virtual machine take into account that the field may be
concurrently updated by another thread.
• For example, suppose an object has a boolean flag done
that is set by one thread and queried by another thread.
• As we already discussed, you can use a lock:
public synchronized boolean isDone() { return done; }
public synchronized void setDone() { done = true; }
private boolean done;
• Perhaps it is not a good idea to use the intrinsic object lock.
• The isDone and setDone methods can block if another
thread has locked the object.
• If that is a concern, one can use a separate Lock just for this
variable.
• But this is getting to be a lot of trouble.
• In this case, it is reasonable to declare the field as volatile:
public boolean isDone() { return done; }
public void setDone() { done = true; }
private volatile boolean done;
• Volatile variables do not provide any atomicity
Deadlocks
• Consider the following situation:
• Account 1: $200
• Account 2: $300
• Thread 1: Transfer $300 from Account 1 to Account 2
• Thread 2: Transfer $400 from Account 2 to Account 1
• Threads 1 and 2 are clearly blocked.
• Neither can proceed because the balances in Accounts 1 and
2 are insufficient.
• Is it possible that all threads are blocked because each is
waiting for more money?
• Such a situation is called a deadlock.
• if you change the run method of the threads to remove the
$1,000 transaction limit, deadlocks can occur quickly.
• Set NACCOUNTS to 10. Construct each transfer runnable with a
max value of 2 * INITIAL_BALANCE and run the program.
• The program will run for a while and then hang.
• A thread blocks indefinitely when it calls the lock method to
acquire a lock that is owned by another thread.
• You can be more cautious about acquiring a lock.
• The tryLock method tries to acquire a lock and returns true if it
was successful.
• Otherwise, it immediately returns false, and the thread can go off
and do something else.
if (myLock.tryLock())
// now the thread owns the lock
try { . . . }
finally { myLock.unlock(); }
else
// do something else
• You can call tryLock with a timeout parameter, like this:
if (myLock.tryLock(100, TimeUnit.MILLISECONDS))
• The stop method is inherently unsafe, and experience has
shown that the suspend method frequently leads to deadlocks
• Hence they are deprecated