Ordered Locks

From CSE231 Wiki
Jump to navigation Jump to search

Background

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 the first of two different types of locks: intrinsic locks.

Every object has an intrinsic lock associated with the object. Intrinsic locks are called with the synchronized keyword.

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).

In this studio we will use lock ordering to avoid deadlock.

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).

Refer to Oracle's official documentation regarding intrinsic locks/synchronization for more information.

Where to Start

Navigate to the src/main/java directory to get started. From there, the classes you will need to modify can be found under the locking.studio package. The core package is composed of utility and building block classes we created for you. Take a look at these classes to get a better understanding of how to use them for your studio.

Lock Ordering

Navigate to LockOrdering.java. As mentioned in the background section, we will attempt to prevent deadlock using a lock ordering algorithm of your making. Essentially, whenever an operation needs to use two separate locks, make sure that the locks are always acquired in a fixed order no matter how the operation is called. The general way to do this is to compare the identity hash codes of the two objects and enforce an order based on those values. However, this can lead to a rare issue in which the two objects are unique, but have the same hashcode.

How would you handle the case where two objects were equal?

Synchronize on a shared third object first for this rare case.

To avoid this issue entirely, however, we have assigned unique bank account numbers to each instance of the bank account object. Thus, you will not need to worry about this edge case.

A couple of notes and common issues:

  • You can use the ReentranLocks associated with the sender and recipient, but we recommend simply using intrinsic locks for this method. In other words, use synchronized(sender) or synchronized(recipient) to make your code simpler.
  • Call the AccountUtils.checkBalanceAndTransfer() method for the sake of simplicity. This method will check that the sender and recipient are not the same person and that the sender has enough money in her account to send the specified amount to the recipient.
  • Use the getUniqueIdNumber() method to get the unique account ID of a given bank account.
  • Your implementation will have to make use of a nested synchronized blocks (one synchronized call within another).