Difference between revisions of "AllOrNothingLocks"

From CSE231 Wiki
Jump to navigation Jump to search
Line 2: Line 2:
 
Check out the [[Lock_Ordering#Background]] studio.
 
Check out the [[Lock_Ordering#Background]] studio.
  
In Java, locks are a synchronization tool that exists to ensure whatever is protected by the lock is only accessed one thread at a time, as to avoid a possible data race. We will explore two different types of locks: intrinsic locks and lock objects.
+
In Java, locks are a synchronization tool that exists to ensure whatever is protected by the lock is only accessed one thread at a time, as to avoid a possible data race. In this studio we will explore the second type of locks: explicitly created <code>Lock</code> objects from the <code>java.util.concurrent.locks</code> package.
  
Every object has an intrinsic lock associated with the object, but lock objects are explicitly defined and instantiated locks which can be called to lock or unlock. Intrinsic locks are called with the <code>synchronized</code> keyword. Lock objects are acted upon just like any other object, but we are mostly interested in the <code>lock()</code> method, the <code>tryLock()</code> method, and the <code>unlock()</code> method. For this studio, we will specifically use the <code>ReentrantLock</code> object, a class which implements the general <code>Lock</code> interface.
+
Every object has an intrinsic lock associated with the object, but <code>Lock</code> objects are explicitly defined and instantiated locks which can be called to lock or unlock. Lock objects are acted upon just like any other object, but we are mostly interested in the <code>lock()</code> method, the <code>tryLock()</code> method, and the <code>unlock()</code> method. For this studio, we will specifically use the <code>ReentrantLock</code> object, a class which implements the general <code>Lock</code> interface.
  
 
To demonstrate how to avoid both data races and deadlock issues, we will create a method designed to transfer money between two bank accounts. Two parties should be able to asynchronously and continuously transfer money between each other without a data race (courtesy of locks) or deadlock (something you will address).
 
To demonstrate how to avoid both data races and deadlock issues, we will create a method designed to transfer money between two bank accounts. Two parties should be able to asynchronously and continuously transfer money between each other without a data race (courtesy of locks) or deadlock (something you will address).
  
The two possible ways to avoid deadlock, which we will address in this studio, are: lock ordering and tryLocking.
+
For this approach, we will make use of the <code>tryLock()</code> method in order to attempt to acquire an object’s lock. If the object’s lock is not available at the time, we will continue to try to acquire the lock until it finally succeeds, after which we can successfully transfer funds from one bank account to another. In this approach, an asynchronous pair of transfers from A to B and B to A will not lead to deadlock as the two processes will give up on acquiring the lock until it becomes available, at which point it will finally acquire the lock. This approach does not make use of ordering, but simply relies on making lock attempts give up if the desired lock is not immediately available (and then try again later).
 
 
With lock ordering, we will control the order in which locks are acquired for two bank accounts using unique identifying values associated with the bank accounts. In this way, a transfer from bank account A to B paired with a simultaneous transfer from B to A will not lead to a scenario in which A is waiting for B’s lock forever while B is waiting for A’s lock forever, as both calls will always attempt to go for A’s lock before B’s or vice versa. For a general object, we might use the object’s hashcode as the unique identifying value, but for this assignment, we will use the account number associated with the bank account (which is guaranteed to be unique, unlike hashcodes).
 
 
 
How would you handle the case where two objects were equal?
 
<spoiler show="spoiler" hide="spoiler">Use a third object</spoiler>
 
 
 
In the other approach, we will make use of the <code>tryLock()</code> method in order to attempt to acquire an object’s lock. If the object’s lock is not available at the time, we will continue to try to acquire the lock until it finally succeeds, after which we can successfully transfer funds from one bank account to another. In this approach, an asynchronous pair of transfers from A to B and B to A will not lead to deadlock as the two processes will give up on acquiring the lock until it becomes available, at which point it will finally acquire the lock. This approach does not make use of ordering, but simply relies on making lock attempts give up if the desired lock is not immediately available (and then try again later).
 
  
 
Refer to Oracle's official documentation regarding [https://docs.oracle.com/javase/tutorial/essential/concurrency/newlocks.html lock] [https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/package-summary.html objects], [https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html reentrant locks], and [https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html intrinsic locks/synchronization] for more information.
 
Refer to Oracle's official documentation regarding [https://docs.oracle.com/javase/tutorial/essential/concurrency/newlocks.html lock] [https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/package-summary.html objects], [https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html reentrant locks], and [https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html intrinsic locks/synchronization] for more information.

Revision as of 18:03, 27 November 2017

Background

Check out the Lock_Ordering#Background studio.

In Java, locks are a synchronization tool that exists to ensure whatever is protected by the lock is only accessed one thread at a time, as to avoid a possible data race. In this studio we will explore the second type of locks: explicitly created Lock objects from the java.util.concurrent.locks package.

Every object has an intrinsic lock associated with the object, but Lock objects are explicitly defined and instantiated locks which can be called to lock or unlock. Lock objects are acted upon just like any other object, but we are mostly interested in the lock() method, the tryLock() method, and the unlock() method. For this studio, we will specifically use the ReentrantLock object, a class which implements the general Lock interface.

To demonstrate how to avoid both data races and deadlock issues, we will create a method designed to transfer money between two bank accounts. Two parties should be able to asynchronously and continuously transfer money between each other without a data race (courtesy of locks) or deadlock (something you will address).

For this approach, we will make use of the tryLock() method in order to attempt to acquire an object’s lock. If the object’s lock is not available at the time, we will continue to try to acquire the lock until it finally succeeds, after which we can successfully transfer funds from one bank account to another. In this approach, an asynchronous pair of transfers from A to B and B to A will not lead to deadlock as the two processes will give up on acquiring the lock until it becomes available, at which point it will finally acquire the lock. This approach does not make use of ordering, but simply relies on making lock attempts give up if the desired lock is not immediately available (and then try again later).

Refer to Oracle's official documentation regarding lock objects, reentrant locks, and intrinsic locks/synchronization for more information.

Where to Start

Navigate to the src/main/java directory to get started.

The locking.core package we used previously in the in Lock_Ordering studio is composed of utility and building block classes we created for you. Review these classes to get a better understanding of how to use them for the first part of this studio.

The classes you will need to modify can be found under the locking.allornothing.studio package.

Try Locking

As mentioned in the background section, this implementation will avoid deadlock by giving up on acquiring a lock if it is not immediately available before coming back to it later (when it might actually be available). This implementation will need to make use of the ReentrantLocks associated with the sender and recipient bank account objects. More specifically, it will have to make use of the tryLock() method. For an example of how to format a tryLock, look below:

Lock lock = new ReentrantLock();
if (lock.tryLock()) {
	try {
		doSomething();
	} finally {
		lock.unlock();
	}
}

BankAccountTryLocking

Open BankAccountTryLocking.java.

A couple of notes and common issues:

  • tryLock() will immediately give up and return false if the lock is not available when it is called. Thus, you will need to construct a loop that will continuously run until the desired lock is available. You can exit out of this loop in two ways: with the break keyword or by returning a valid value for the method.
  • Anytime you lock a lock object, you must use a try/finally block to unlock the lock.
  • We recommend you call the BankAccount.checkBalanceAndTransfer() method for the sake of simplicity. This method will check that the sender and recipient are not the same people and that the sender has enough money in her account to send the specified amount to the recipient.
  • Your implementation will have to make use of a nested try/finally block (one try/finally call within another).

LockUtils

Open LockUtils.java.