This lab exercise will give you practice working with Java threads.

You will take a set of classes that simulate the use of a bank account by multiple users and modify it to give the simulation realistic, multi-threaded behavior.

Basic Requirements

The program allows control of the following aspects of a bank account: We imagine a scenario in which a number of siblings are given access to a bank account by a parent.

For each simulation run:

In the version of the program you are given, the siblings' transaction lists are processed in series — a sibling carries out all of the transactions on its list before another sibling can carry out its transactions.

This creates two deficiencies:

Below is a screen shot of the simulation after clicking RUN with two siblings.

Although all transactions for each sibling are carried out, all of Sibling 1's transactions are carried out before any of Sibling 2's transactions, which is unfair to Sibling 2.

Below is a screen shot showing what happens when a withdrawal would cause the balance to go negative. Note that an error is signalled and any remaining transactions are cancelled.

This is not optimal, since if Sibling 2 had been allowed to make a transaction, it may not have caused a negative balance.

You will correct these deficiences by making each sibling's transaction processing occur in a separate thread, making sure to avoid potential race conditions and deadlocks.

Additionally, you will add a parent thread that "rescues" the siblings from deadlock by making deposits when appropriate. Here are the related requirements:

In the simplest case, no sibling attempts an illegal withdrawal, but the transactions occur in parallel:

When one sibling attempts an illegal withdrawal, its thread waits until the balance is increased, perhaps by another sibling making a scheduled deposit:

When all siblings are trying to make an illegal withdrawal, then there is deadlock, and the parent thread makes a $100 deposit, as in the next screenshot, which first shows one sibling bailing out another, and then the parent rescuing both. The bottom screenshot shows that the parent may need to make multiple deposits during the course of the simulation.

Shown below is an annotated class diagram of the simulation application.

The menu shows the classes you are given.

Simulation.java

SimulationControl.java

LogView.java

BankAccount.java

BankAccountUser.java

First, create a new NetBeans project: The exercise will proceed in three steps:
  1. Thread the program
  2. Synchronize the threads
  3. Rescue the threads from deadlocks
The BankAccountUser class has code in its run method that we want to run in a separate thread.

In this step, you will simply thread the program (without synchronizing) and observe the results.

One way to make the run method execute in separate threads is to make some changes to BankAccountUser and SimulationControl.

Introducing threads will also require a change to the LogView class.

SimulationControl and LogView make use of JavaFX, which we have yet to discuss. However, you can make the changes required without fully understanding JavaFX at this time.

Declare BankAccountUser to extend the Thread class.

Since the Thread class already has a getName method:

This hands the duty of storing name to the superclass (Thread).

At the bottom of BankAccountUser's run method's for loop, just before repeating, make a call to Thread.sleep. A call like

may produce perfectly interleaved behavior, while

will produce more realistic results.

Either way will require handling a possible InterruptedException

In SimulationControl, change the call user.run() to user.start().
The creation of multiple threads introduces synchronization issues with the JavaFX thread. We will learn more about JavaFX later, but to avoid these issues, in LogView.java replace the line:

with:

Do a Clean and Build on your project to make sure these changes take effect.

Now try running the simulation.

For one sibling:
For multiple siblings, by sheer luck all transactions might get completed:

Otherwise, if one sibling attempts to make an illegal withdrawal, it will cause an exception in its thread, but another thread might be able to continue and even finish:

In this case, if Sibling 1 had not caused an exception, it might have continued after Sibling 2 changed the account balance.

To fix this, the threads need to be synchronized.

In this step you will eliminate the runtime exceptions by synchronizing the threads using the Object class's wait() and notifyAll() methods, and observe the results.
When a sibling thread attempts to make an illegal withdrawal, instead of throwing an exception it should wait while other threads possibly change the balance.

So the withdraw and deposit methods of the BankAccount class need to be modified:

For one sibling, everything is fine if no illegal withdrawals are made. If one is attempted, there will be no exception, but the thread will wait for an event that cannot occur — deadlock.

For multiple siblings, if one attempts an illegal withdrawal, it may get bailed out by another sibling. In the simulation below, Sibling 1 attempts an illegal withdrawal, waits, and continues after Sibling 2 makes a deposit. Sibling 2 eventually finishes, but Sibling 1 reaches deadlock:

In the simulation below, both siblings reach deadlock, so the program hangs. In the next step, you will eliminate the possibility of deadlock.

When you successfully complete this step, you will have synchronized the sibling threads, but you will not have eliminated the possibility of deadlock, which is addressed in Step 3.

So it may happen that a test run (the result of clicking the Run button) will end in a deadlock. If this happens, and you want to run another test, you must force the application to quit and then restart it. If you do not, the simulation will be in an inconsistent state and produce confusing results.

If simply closing the simulation window does not stop the application, you will notice that the label "Threads (run-single)" persists in the status line near the lower right corner of the IDE. You can force a quit by clicking the small box labeled with "x" near this label. NetBeans will put up a dialog to confirm your intention to quit.

In this step you will add a thread whose sole purpose is to rescue the sibling threads from deadlock.

This thread, analogous to the siblings' parent, will be executed by the run method of a new BankAccountRescuer class, which will extend BankAccountUser.

The BankAccountRescuer thread will:

Upon completion of this step the overall class diagram will look as shown below. You will add a new BankAccountRescuer class and make minor changes to some existing classes.

The parent thread must be able to detect when all non-finished sibling threads are waiting. Begin by making these modifications:
The SimulationControl class creates and starts all the sibling threads. It should be modified to also create and start the parent thread.

SimulationControl uses JavaFX components, which we will learn about later. However, the modifications you need to make do not require understanding JavaFX at this time.

To add the parent thread:

The program should now display the desired behavior.
When your bank account simulation is working correctly: Note the general Submission Policy in the menu at left.
Successful completion of: