|
| |
However, we are locking at a fairly coarse level.
We might want to move the locking to a finer granularity.
We can use the trylock() method:
boolean tryLock()
- Acquires the lock only if it is free at the time of invocation.
Acquires the lock if it is available and returns immediately with the
value true. If the lock is not available then this method will
return immediately with the value false.
A typical usage idiom for this method would be:
Lock lock = ...;
if (lock.tryLock())
{
try
{
// manipulate protected state
}
finally
{
lock.unlock();
}
}
else
{
// perform alternative actions
}
This usage ensures that the lock is unlocked if it was acquired, and doesn't
try to unlock if the lock was not acquired.
-
Returns:
- true if the lock was acquired and false otherwise.
Here's an example of how this might be done:
package threads;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* This class will use an explicit lock to avoid a deadlock situation.
*/
public class ExplicitLockAccountTransfer
{
public static void main(String[] args)
{
Account a1 = new Account("Account 1", 10000);
Account a2 = new Account("Account 2", 30000);
System.out.println("Initial Balances:");
System.out.println(" Account 1: " + a1.getBalance());
System.out.println(" Account 2: " + a2.getBalance());
System.out.println("Transferring 5 from Account 1 to Account 2");
System.out.println("Transferring 20 from Account 2 to Account 1");
Thread threadA =
new TransferFundsThread("Thread A", a1, a2, 5);
Thread threadB =
new TransferFundsThread("Thread B", a2, a1, 20);
threadA.start();
threadB.start();
}
}
class Account
{
public Account(String name, int balance)
{
m_name = name;
m_balance = balance;
}
public int getBalance()
{
return m_balance;
}
public void setBalance(int newBalance)
{
m_balance = newBalance;
}
public String getName()
{
return m_name;
}
public Lock getLock()
{
return m_lock;
}
private String m_name;
private int m_balance;
private Lock m_lock = new ReentrantLock();
}
class TransferFundsThread extends Thread
{
public TransferFundsThread(String name,
Account from,
Account to,
int amount)
{
super(name);
m_from = from;
m_to = to;
m_amount = amount;
}
public void run()
{
System.out.println("run: Transferring funds from " + m_from.getName() +
" to " + m_to.getName());
transferFunds();
}
/**
* In order to transfer funds safely, we need to be able
* to hold a lock on the single lock used to synchronize
* access to the transferFunds method.
*/
private void transferFunds()
{
while (true)
{
try
{
// Try to acquire the lock for the from account
if ( m_from.getLock().tryLock(500, TimeUnit.MILLISECONDS) )
{
try
{
// Get the balance from the from account
int balance = getBalance();
// Try to acquire the lock for the to account
if ( m_to.getLock().tryLock(500, TimeUnit.MILLISECONDS) )
{
try
{
doTransfer(balance);
}
finally
{
// Unlock the to lock, regardless of whether
// an exception occurred.
m_to.getLock().unlock();
}
// We succeeded, so break out of the for while loop.
break;
}
}
finally
{
// Unlock the from lock, regardless of whether
// an exception occurred.
m_from.getLock().unlock();
}
}
}
catch (InterruptedException ie)
{
// Do nothing; try again
}
}
}
/**
* Get the balance from the to account
*/
private int getBalance()
{
System.out.println(getName() +
": Getting balance from " +
m_from.getName());
int fromBalance = m_from.getBalance();
System.out.println(getName() +
": Balance from " +
m_from.getName() +
" = " + fromBalance);
return fromBalance;
}
/**
* Do the actual transfer of funds
*/
private void doTransfer(int fromBalance)
{
System.out.println(getName() +
": Getting balance from " +
m_to.getName());
int toBalance = m_to.getBalance();
System.out.println(getName() +
": Balance from " +
m_to.getName() +
" = " + toBalance);
if (fromBalance >= m_amount)
{
System.out.println(getName() +
": Withdrawing funds from " +
m_from.getName());
m_from.setBalance(fromBalance - m_amount);
System.out.println(getName() +
": Depositing funds into " +
m_to.getName());
m_to.setBalance(toBalance + m_amount);
System.out.println(getName() +
": New balances are:\n" +
" " + m_from.getName() +
" = " +
m_from.getBalance() + "\n" +
" " + m_to.getName() + " = " +
m_to.getBalance() );
}
}
/// Private data ///
private Account m_from;
private Account m_to;
private int m_amount;
}
|
Which, when run, produces the following output:
Initial Balances:
Account 1: 10000
Account 2: 30000
Transferring 5 from Account 1 to Account 2
Transferring 20 from Account 2 to Account 1
run: Transferring funds from Account 2 to Account 1
Thread B: Getting balance from Account 2
Thread B: Balance from Account 2 = 30000
Thread B: Getting balance from Account 1
Thread B: Balance from Account 1 = 10000
Thread B: Withdrawing funds from Account 2
Thread B: Depositing funds into Account 1
Thread B: New balances are:
Account 2 = 29980
Account 1 = 10020
run: Transferring funds from Account 1 to Account 2
Thread A: Getting balance from Account 1
Thread A: Balance from Account 1 = 10020
Thread A: Getting balance from Account 2
Thread A: Balance from Account 2 = 29980
Thread A: Withdrawing funds from Account 1
Thread A: Depositing funds into Account 2
Thread A: New balances are:
Account 1 = 10015
Account 2 = 29985 This still happens to cause the threads to be
serialized, but shows the use of locks of smaller granularity.
|