Main Page

Previous Next

Managing Threads

In both the examples we've seen in this chapter, the threads are launched and then left to compete for computer resources. Because all three threads compete in an uncontrolled way for the processor, the output from the threads gets muddled. This isn't normally a desirable feature in a program. In most instances where you use threads, the way in which they execute will need to be managed so that they don't interfere with each other.

Of course, in our examples, the programs are deliberately constructed to release control of the processor part way through outputting a name. While this is very artificial, similar situations can arise in practice, particularly where threads are involved in a repetitive operation. It is important to appreciate that a thread can be interrupted while a source statement is executing. For instance, imagine that a bank teller is crediting a check to an account and at the same time the customer with that account is withdrawing some cash through an ATM machine. This might happen in the following way:

  • The bank teller checks the balance of the customer's account, which is $500.

  • The ATM machine asks for the account balance.

  • The teller adds the value of the check, $100, to the account balance to give a figure of $600.

  • The ATM machine takes $50 off the balance of $500, which gives a figure of $450, and spits out 5 $10 bills.

  • The teller assigns the value of $600 to the account balance.

  • The ATM machine assigns the value $450 to the account balance.

Here you can see the problem very well. Asking the account for its balance and assigning a new balance to the account are two different operations. As long as this is the case, we can never guarantee that this type of problem will not occur.

Where two or more threads share a common resource, such as a file or a block of memory, you'll need to take steps to ensure that one thread doesn't modify a resource while that resource is still being used by another thread. Having one thread update a record in a file while another thread is part way through retrieving the same record is a recipe for disaster. One way of managing this sort of situation is to use synchronization for the threads involved.

Synchronization

The objective of synchronization is to ensure that, when several threads want access to a single resource, only one thread can access it at any given time. There are two ways in which you can use synchronization to manage your threads of execution:

  • You can manage code at the method level – this involves synchronizing methods.

  • You can manage code at the block level – using synchronizing blocks.

We'll look at how we can use synchronized methods first.

Synchronized Methods

You can make a subset (or indeed all) of the methods for any class object mutually exclusive, so that only one of the methods can execute at any given time. You make methods mutually exclusive by declaring them in the class using the keyword synchronized. For example:

class MyClass {
  synchronized public void method1() {
    // Code for the method...
  }

  synchronized public void method2() {
    // Code for the method...
  }

  public void method3() {
    // Code for the method...
  }
}

Now, only one of the synchronized methods in a class object can execute at any one time. Only when the currently executing synchronized method for an object has ended can another synchronized method start for the same object. The idea here is that each synchronized method has guaranteed exclusive access to the object while it is executing, at least so far as the other synchronized methods for the class object are concerned.

The synchronization process makes use of an internal lock that every object has associated with it. The lock is a kind of flag that is set by a process, referred to as locking or a lock action, when a synchronized method starts execution. Each synchronized method for an object checks to see whether the lock has been set by another method. If it has, it will not start execution until the lock has been reset by an unlock action. Thus, only one synchronized method can be executing at one time, because that method will have set the lock that prevents any other synchronized method from starting.

Important 

Note that there's no constraint here on simultaneously executing synchronized methods for two different objects of the same class. It's only concurrent access to any one object that is controlled.

Of the three methods in myClass, two are declared as synchronized, so for any object of the class, only one of these methods can execute at one time. The method that isn't declared as synchronized, method3(), can always be executed by a thread, regardless of whether a synchronized method is executing.

It's important to keep clear in your mind the distinction between an object that has instance methods that you declared as synchronized in the class definition and the threads of execution that might use them. A hypothetical relationship between three threads and two objects of the class myClass is illustrated in the following diagram:

Click To expand

The numbers on the arrows in the diagram indicate the sequence of events. No! indicates that the thread waits until the method is unlocked so it can execute it. While method1() in obj2 is executing, method2() for the same object can't be executed. The synchronization of these two instance methods in an object provides a degree of protection for the object, in that only one synchronized method can mess with the data in the object at any given time.

However, each object is independent of any other object when it comes to synchronized instance methods. When a thread executes a synchronized method for an object, it is assured exclusive access to the object insofar as the synchronized methods in that object are concerned. Another thread, though, can still call the same method for a different object. While method1() is being executed for obj1, this doesn't prevent method1() for obj2 being executed by some other thread. Also, if there's a method in an object that has not been declared as synchronized – method3() in obj1 for example – any thread can call that at any time, regardless of the state of any synchronized methods in the object.

If you apply synchronization to static methods in a class, only one of those static methods in the class can be executing at any point in time, and this is per class synchronization and the class lock is independent of any locks for objects of the class.

An important point of principle that you need to understand is that the only method that is necessarily part of a thread in a class object that represents a thread is the run()method. Other methods for the same class object are only part of the thread if they are called directly or indirectly by the run() method. All the methods that are called directly or indirectly from the run()method for an object are all part of the same thread, but they clearly don't have to be methods for the same Thread object. Indeed they can be methods that belong to any other objects, including other Thread objects that have their own run() methods.

Using Synchronized Methods

To see how synchronization can be applied in practice, we'll construct a program that provides a simple model of a bank. Our bank is a very young business with only one customer account initially, but we'll have two clerks, each working flat out to process transactions for the account, one handling debits and the other handling credits. The objects in our program are illustrated here:

Click To expand

The bank in our model is actually a computer that performs operations on the account, and the account is stored separately. Each clerk can communicate directly with the bank. We'll be defining four classes that we will use in our program to model banking operations:

  • A Bank class to represent the bank computer.

  • An Account class to represent the account at the bank.

  • A Transaction class to represent a transaction on the account – a debit or a credit for instance.

  • A Clerk class to represent a bank clerk.

We will also define a class containing the method main() that will start the process off and determine how it all works.

Important 

As we develop the code, we won't necessarily get it right first time, but we will improve as we find out more about how to program using threads. This will expose some of the sorts of errors and complications that can arise when you're programming using threads.

Try It Out – Defining a Bank Class

The bank computer is the agent that will perform the operations on an account so we will start with that. We can define the Bank class that will represent this as:

// Define the bank
class Bank {
  // Perform a transaction
  public void doTransaction(Transaction transaction) {
    int balance = transaction.getAccount().getBalance();   // Get current balance

    switch(transaction.getTransactionType()) {
      case Transaction.CREDIT:
        // Credits require a lot of checks...
        try {
          Thread.sleep(100);

        } catch(InterruptedException e) {
          System.out.println(e);
        }
        balance += transaction.getAmount();         // Increment the balance
        break;

      case Transaction.DEBIT:
        // Debits require even more checks...
        try {
          Thread.sleep(150);

        } catch(InterruptedException e) {
          System.out.println(e);
        }
        balance -= transaction.getAmount();         // Decrement the balance
        break;

      default:                                      // We should never get here
        System.out.println("Invalid transaction");
        System.exit(1);
   }
    transaction.getAccount().setBalance(balance);   // Restore the account balance
  }
}

How It Works

The Bank class is very simple. It keeps no records of anything locally as the accounts will be identified separately, and it only has one method that carries out a transaction. The Transaction object will provide all the information about what the transaction is, and to which account it applies. We have only provided for debit and credit operations on an account, but the switch could easily be extended to accommodate other types of transactions. Both of the transactions supported involve some delay while the standard nameless checks and verifications, that all banks have, are carried out. The delay is simulated by calling the sleep() method belonging to the Thread class.

Of course, during this time, other things in other threads may be going on. There are no instance variables to initialize in a Bank object so we don't need a constructor. Since our Bank object works using a Transaction object, let's define the class for that next.

Try It Out – Defining a Transaction on an Account

The Transaction class could represent any transaction on an account, but we are limiting ourselves to debits and credits. We can define the class as:

class Transaction {
  // Transaction types
  public static final int DEBIT = 0;
  public static final int CREDIT = 1;
  public static String[] types = {"Debit","Credit"};

  // Constructor
  public Transaction(Account account, int transactionType, int amount) {
    this.account = account;
    this.transactionType = transactionType;
    this.amount = amount;
  }

  public Account getAccount() {
    return account;  
  }

  public int getTransactionType() {
    return transactionType;  
  }

  public int getAmount() {
    return amount;  
  }

  public String toString() {
    return types[transactionType] + " A//C: " + ": $" + amount;
  }

  private Account account;
  private int amount;
  private int transactionType;
}

How It Works

The type of transaction is specified by the transactionType field that must be one of the values defined for transaction types. We should build in checks in the constructor to ensure only valid transactions are created, but we'll forego this to keep the code volume down, and you certainly know how to do this sort of thing by now. A transaction records the amount for the transaction and a reference to the account to which it applies, so a Transaction object specifies a complete transaction. The methods are very straightforward, just accessor methods for the data members that are used by the Bank object, plus the toString() method in case we need it.

Try It Out – Defining a Bank Account

We can define an account as:

// Defines a customer account
public class Account {
  // Constructor
  public Account(int accountNumber, int balance) {
    this.accountNumber = accountNumber;            // Set the account number
    this.balance = balance;                        // Set the initial balance
  }

  // Return the current balance
  public int getBalance() {
    return balance;  
  }

  // Set the current balance
  public void setBalance(int balance) {
    this.balance = balance;  
  }

  public int getAccountNumber() {
    return accountNumber;  
  }

  public String toString() {
    return "A//C No. "+accountNumber+" : $"+balance;
  }

  private int balance;                             // The current account balance
  private int accountNumber;                       // Identifies this account
}

How It Works

The Account class is also very simple. It just maintains a record of the amount in the account as a balance, and provides methods for retrieving and setting the current balance. Operations on the account are performed externally by the Bank object. We have a bit more than we need in the Account class at the moment, but the methods we don't use in the current example may be useful later.

Try It Out – Defining a Bank Clerk

A clerk is a slightly more complicated animal. He or she retains information about the bank and details of the current transaction and is responsible for initiating debits and credits on an account by communication with the central bank. Each clerk will work independently of the others so they will each be a separate thread:

public class Clerk implements Runnable {
  private Bank theBank;               // The employer - an electronic marvel
  private Transaction inTray;         // The in-tray holding a transaction
  // Constructor
  public Clerk(Bank theBank) {
    this.theBank = theBank;           // Who the clerk works for
    inTray = null;                    // No transaction initially
  }

  // Receive a transaction
  public void doTransaction(Transaction transaction) {
    inTray = transaction;  
  }

  // The working clerk...
  public void run() {
    while(true) {
      while(inTray == null) {         // No transaction waiting?
        try {
          Thread.sleep(150);          // Then take a break...

        } catch(InterruptedException e) {
          System.out.println(e);
        }
      }
 
      theBank.doTransaction(inTray);
      inTray = null;                  // In-tray is empty
    }
  }

  // Busy check
  public boolean isBusy() {
    return inTray != null;            // A full in-tray means busy!
  }
}

How It Works

A Clerk object is a thread since it implements the Runnable interface. Each clerk has an in-tray, capable of holding one transaction, and while the in-tray is not null, the clerk is clearly busy. A clerk needs to be aware of the Bank object that is employing him or her, so a reference is stored in theBank when a Clerk object is created. A transaction is placed in the in-tray for a clerk by calling his or her doTransaction() method. You can check whether a clerk is busy by calling the isBusy() member which will return true if a transaction is still in progress.

The real work is actually done in the run() method. If the in-tray is empty, indicated by a null value in inTray, then there's nothing to do, so after sleeping a while the loop goes around again for another look at the in-tray. When a transaction has been recorded, the method in theBank object is called to carry it out and the inTray is reset to null.

All we need now is the class to drive our model world, which we will call BankOperation. This class only requires the method main(), but there are quite a lot of things to do in this method so we'll put it together piece by piece.

Try It Out – Defining the Operation of the Bank

Apart from setting everything up, the main() method has to originate transactions on the accounts and pass them on to the clerks to be expedited. We will start with just one account and a couple of clerks. Here's the basic structure:

import java.util.Random;

public class BankOperation {
  public static void main(String[] args) {
    int initialBalance = 500;     // The initial account balance
    int totalCredits = 0;         // Total credits on the account
    int totalDebits =0;           // Total debits on the account
    int transactionCount = 20;    // Number of debits and credits

    // Create the account, the bank, and the clerks...

    // Create the threads for the clerks as daemon, and start them off

    // Generate the transactions of each type and pass to the clerks

    // Wait until both clerks are done

    // Now output the results
  }
}

The import for the Random class is there because we will need it in a moment. To create the Bank object, the clerks, and the account, we need to add the following code:

// Create the account, the bank, and the clerks...
Bank theBank = new Bank();                         // Create a bank
Clerk clerk1 = new Clerk(theBank);                 // Create the first clerk
Clerk clerk2 = new Clerk(theBank);                 // Create the second clerk
Account account = new Account(1, initialBalance);  // Create an account

The next step is to add the code to create the threads for the clerks and start them going:

// Create the threads for the clerks as daemon, and start them off
Thread clerk1Thread = new Thread(clerk1);
Thread clerk2Thread = new Thread(clerk2);
clerk1Thread.setDaemon(true);                    // Set first as daemon
clerk2Thread.setDaemon(true);                    // Set second as daemon
clerk1Thread.start();                            // Start the first
clerk2Thread.start();                            // Start the second

The code to generate the transactions looks a lot, but is quite repetitive:

// Generate transactions of each type and pass to the clerks
Random rand = new Random();                      // Random number generator
Transaction transaction;                         // Stores a transaction
int amount = 0;                                  // stores an amount of money
for(int i = 1; i <= transactionCount; i++) {
  amount = 50 + rand.nextInt(26);                // Generate amount of $50 to $75
  transaction = new Transaction(account,            // Account
                                Transaction.CREDIT, // Credit transaction
                                amount);            //  of amount
  totalCredits += amount;                           // Keep total credit tally

  // Wait until the first clerk is free
  while(clerk1.isBusy()) {
    try {
      Thread.sleep(25);                            // Busy so try later

    } catch(InterruptedException e) {
      System.out.println(e);
    }
  }
  clerk1.doTransaction(transaction);             // Now do the credit

  amount = 30 + rand.nextInt(31);                // Generate amount of $30 to $60
  transaction = new Transaction(account,            // Account
                                Transaction.DEBIT,  // Debit transaction
                                amount);            //  of amount
  totalDebits += amount;                         // Keep total debit tally
  // Wait until the second clerk is free
  while(clerk2.isBusy()) {
    try {
      Thread.sleep(25);                            // Busy so try later

    } catch(InterruptedException e) {
      System.out.println(e);
    }
  }
  clerk2.doTransaction(transaction);             // Now do the debit
}

Once all the transactions have been processed, we can output the results. However, the clerks could still be busy after we exit from the loop, so we need to wait for both of them to be free before outputting the results. We can do this with a while loop:

// Wait until both clerks are done
while(clerk1.isBusy() || clerk2.isBusy()) {
  try {
    Thread.sleep(25);

  } catch(InterruptedException e) {
    System.out.println(e);
  }
}

Lastly, we output the results:

// Now output the results
System.out.println(
          "Original balance : $" + initialBalance+"\n" +
          "Total credits    : $" + totalCredits+"\n" +
          "Total debits     : $" + totalDebits+"\n" +
          "Final balance    : $" + account.getBalance() + "\n" +
          "Should be        : $" + (initialBalance + totalCredits - totalDebits));

How It Works

The variables in the main() method track the total debits and credits, and record the initial account balance. They are there to help us figure out what has happened after the transactions have been processed. The number of times we debit and then credit the account is stored in transactionCount, so the total number of transactions will be twice this value. We have added five further blocks of code to perform the functions indicated by the comments, so let's now go through each of them in turn.

The Account object is created with the account number as 1 and with the initial balance stored in initialBalance. We pass the bank object, theBank, to the constructor for each of the Clerk objects, so that they can record it.

The Thread constructor requires an object of type Runnable, so we can just pass the Clerk objects in the argument. There's no problem in doing this because the Clerk class implements the interface Runnable. You can always implicitly cast an object to a type that is any superclass of the object or any interface type that the object class implements.

All the transactions are generated in the for loop. The handling of debits is essentially the same as the handling of credits, so we'll only go through the code for the latter in detail. A random amount between $50 and $75 is generated for a credit transaction by using the nextInt() method for the rand object of type Random that we create. You'll recall that nextInt() returns an int value in the range 0 to one less than the value of the argument, so by passing 26 to the method, we get a value between 0 and 25 returned. We add 50 to this and, hey presto, we have a value between 50 and 75. We then use this amount to create a Transaction object that represents a credit for the account. To keep a check on the work done by the clerks, we add this credit to the total of all the credits generated that is stored in the variable totalCredits. This will allow us to verify whether or not the account has been updated properly.

Before we pass the transaction to clerk1, we must make sure that he or she isn't busy. Otherwise we would overwrite the clerk's in-tray. The while loop does this. As long as the isBusy() method returns true, we continue to call the sleep() method for a 25 millisecond delay, before we go round and check again. When isBusy() returns false, we call the doTransaction()method for the clerk with the reference to the transaction object as the argument. The for loop will run for 20 iterations, so we'll generate 20 random transactions of each type.

The third while loop works in the same way as the previous check for a busy clerk – the loop continues if either of the clerks is busy.

Lastly, we output the original account balance, the totals of credits and debits, and the final balance plus what it should be for comparison. That's all we need in the method main(), so we're ready to give it a whirl. Remember that all four classes need to be in the same directory.

Running the Example

Now, if you run the example, the final balance will be wrong. You should get results something like the following:

Original balance    : $500
Total credits       : $1252
Total debits        : $921
Final balance       : $89
Should be           : $831

Of course, your results won't be the same as this, but they should be just as wrong. The customer will not be happy. His account balance is seriously out – in the bank's favor, of course, as always. So how has this come about?

The problem is that both clerks are operating on the same account at the same time. Both clerks call the doTransaction() method for the Bank object, so this method is executed by both clerk threads. Separate calls on the same method are overlapping.

Try It Out – Synchronizing Methods

One way we can fix this is by simply declaring the method that operates on an account as synchronized. This will prevent one clerk getting at it while it is still in progress with the other clerk. To implement this you should amend the Bank class definition as follows:

// Define the bank
class Bank {
  // Perform a transaction
  synchronized public void doTransaction(Transaction transaction) {
    // Code exactly as before...
  }
}

How It Works

Declaring this method as synchronized will prevent a call to it from being executed while another is still in operation. If you run the example again with this change, the result will be something like:

Original balance    : $500
Total credits       : $1201
Total debits        : $931
Final balance       : $770
Should be           : $770

The amounts may be different because the transaction amounts are random, but your final balance should be the same as adding the credits to the original balance and subtracting the debits.

As we saw earlier, when you declare methods in a class as synchronized, it prevents concurrent execution of those methods within a single object, including concurrent execution of the same method. It is important not to let the fact that there is only one copy of a particular method confuse you. A given method can be potentially executing in any number of threads – as many threads as there are in the program in fact. If it was not synchronized, the doTransaction() method could be executed concurrently by any number of clerks.

Although this fixes the problem we had in that the account balance is now correct, the bank is still amazingly inefficient. Each clerk is kicking their heels while another clerk is carrying out a transaction. At any given time a maximum of one clerk is working. On this basis the bank could sack them all bar one and get the same throughput. We can do better, as we shall see.

Synchronizing Statement Blocks

In addition to being able to synchronize methods on a class object, you can also specify a statement or a block of code in your program as synchronized. This is more powerful, since you specify which particular object is to benefit from the synchronization of the statement or code block, not just the object that contains the code as in the case of a synchronized method. Here we can set a lock on any object for a given statement block. When the block that is synchronized on the given object is executing, no other code block or method that is synchronized on the same object can execute. To synchronize a statement, you just write:

synchronized(theObject)
  statement;            // Synchronized with respect to theObject

No other statements or statement blocks in the program that are synchronized on the object theObject can execute while this statement is executing. Naturally, this applies even when the statement is a call to a method, which may in turn call other methods. The statement here could equally well be a block of code between braces. This is powerful stuff. Now we can lock a particular object while the code block that is working is running.

To see precisely how you can use this in practice, let's create a modification of the last example. Let's up the sophistication of our banking operation to support multiple accounts. To extend our example to handle more than one account, we just need to make some changes to main(). We'll add one extra account to keep the output modest, but we'll modify the code to handle any number of accounts.

Try It Out – Handling Multiple Accounts

We can modify the code in main() that creates the account and sets the initial balance to create multiple accounts as follows:

public class BankOperation {
  public static void main(String[] args) {
    int[] initialBalance = {500, 800};          // The initial account balances
    int[] totalCredits = new int[initialBalance.length]; //Two different cr totals
    int[] totalDebits = new int[initialBalance.length];  //Two different db totals
    int transactionCount = 20;                  // Number of debits and of credits

    // Create the bank and the clerks...
    Bank theBank = new Bank();                  // Create a bank
    Clerk clerk1 = new Clerk(theBank );         // Create the first clerk
    Clerk clerk2 = new Clerk(theBank );         // Create the second clerk

    // Create the accounts, and initialize total credits and debits
    Account[] accounts = new Account[initialBalance.length]; 
    for(int i = 0; i < initialBalance.length; i++) {
      accounts[i] = new Account(i+1, initialBalance[i]); // Create accounts
      totalCredits[i] = totalDebits[i] = 0;
    }

    // Create the threads for the clerks as daemon, and start them off

    // Create transactions randomly distributed between the accounts

    // Wait until both clerks are done

    // Now output the results
  }
}

We now create an array of accounts in a loop, the number of accounts being determined by the number of initial balances in the initialBalance array. Account numbers are assigned successively starting from 1. The code for creating the bank and the clerks and for creating the threads and starting them is exactly the same as before. The shaded comments that follow the code indicate the other segments of code in main() that we need to modify.

The next piece we need to change is the creation and processing of the transactions:

// Create transactions randomly distributed between the accounts
Random rand = new Random();
Transaction transaction;                         // Stores a transaction
int amount = 0;                                  // Stores an amount of money
int select = 0;                                  // Selects an account
for(int i = 1; i <= transactionCount; i++) {
  // Choose an account at random for credit operation
  select = rand.nextInt(accounts.length);
  amount = 50 + rand.nextInt(26);                // Generate amount of $50 to $75
  transaction = new Transaction(accounts[select],       // Account
                                    Transaction.CREDIT, // Credit transaction
                                    amount);            //  of amount
  totalCredits[select] += amount;                // Keep total credit tally

  // Wait until the first clerk is free
  while(clerk1.isBusy()) {
    try {
      Thread.sleep(25);                            // Busy so try later
    }
    catch(InterruptedException e)
    {
      System.out.println(e);
    }
  }
  clerk1.doTransaction(transaction);             // Now do the credit

  // choose an account at random for debit operation
  select = rand.nextInt(accounts.length);
  amount = 30 + rand.nextInt(31);                // Generate amount of $30 to $60
  transaction = new Transaction(accounts[select],   // Account
                                Transaction.DEBIT,  // Debit transaction
                                amount);            //  of amount
  totalDebits[select] += amount;                 // Keep total debit tally

  // Wait until the second clerk is free
  while(clerk2.isBusy()) {
    try {
      Thread.sleep(25);                         // Busy so try later

    } catch(InterruptedException e) {
      System.out.println(e);
    }
  }
  clerk2.doTransaction(transaction);             // Now do the debit
}

The last modification we must make to the method main() is for outputting the results. We now do this in a loop seeing as we have to process more than one account:

for(int i = 0; i < accounts.length; i++)   
  System.out.println("Account Number:"+accounts[i].getAccountNumber()+"\n"+
     "Original balance    : $" + initialBalance[i] + "\n" +
     "Total credits       : $" + totalCredits[i] + "\n" +
     "Total debits        : $" + totalDebits[i] + "\n" +
     "Final balance       : $" + accounts[i].getBalance() + "\n" +
     "Should be           : $" + (initialBalance[i] 
                               + totalCredits[i]
                               - totalDebits[i]) + "\n");

This is much the same as before except that we now extract values from the arrays we have created. If you run this version it will, of course, work perfectly. A typical set of results is:

Account Number:1
Original balance    : $500
Total credits       : $659
Total debits        : $614
Final balance       : $545
Should be           : $545

Account Number:2
Original balance    : $800
Total credits       : $607
Total debits        : $306
Final balance       : $1101
Should be           : $1101

How It Works

We now allocate arrays for the initial account balances, the totals of credits and debits for each account, and the totals for the accounts themselves. The number of initializing values in the initialBalance[] array will determine the number of elements in each of the arrays. In the for loop, we create each of the accounts with the appropriate initial balance, and initialize the totalCredits[] and totalDebits[] arrays to zero.

In the modified transactions loop, we select the account from the array for both the debit and the credit transactions by generating a random index value that we store in the variable select. The index select is also used to keep a tally of the total of the transactions of each type.

This is all well and good, but by declaring the methods in the class Bank as synchronized, we're limiting the program quite significantly. No operation of any kind can be carried out while any other operation is in progress. This is unnecessarily restrictive since there's no reason to prevent a transaction on one account while a transaction for a different account is in progress. What we really want to do is constrain the program to prevent overlapping of operations on the same account, and this is where declaring blocks of code to be synchronized on a particular object can help.

Let's consider the methods in the class Bank once more. What we really want is the code in the doTransaction() method to be synchronized so that simultaneous processing of the same account is prevented, not so that processing of different accounts is inhibited. What we need to do is synchronize the processing code for a transaction on the Account object that is involved.

Try It Out – Applying synchronized Statement Blocks

We can do this with the following changes:

class Bank {
  // Perform a transaction
  public void doTransaction(Transaction transaction) {
    switch(transaction.getTransactionType()) {
      case Transaction.CREDIT:
        synchronized(transaction.getAccount()) {
           // Get current balance
           int balance = transaction.getAccount().getBalance();  

          // Credits require require a lot of checks...
          try {
            Thread.sleep(100);

          } catch(InterruptedException e) {
            System.out.println(e);
          }
          balance += transaction.getAmount();           // Increment the balance
          transaction.getAccount().setBalance(balance); // Restore account balance
          break;
        }

      case Transaction.DEBIT:
        synchronized(transaction.getAccount()) {
          // Get current balance
          int balance = transaction.getAccount().getBalance(); 
          // Debits require even more checks...
          try {
            Thread.sleep(150);

          } catch(InterruptedException e) {
            System.out.println(e);
          }
          balance -= transaction.getAmount();          // Increment the balance...
          transaction.getAccount().setBalance(balance);// Restore account balance
          break;
        }

      default:                                         // We should never get here
        System.out.println("Invalid transaction");
        System.exit(1);
    }
  }
}

How It Works

The expression in parentheses following the keyword synchronized specifies the object for which the synchronization applies. Once one synchronized code block is entered with a given account object, no other code block or method can be entered that has been synchronized on the same object. For example, if the block performing credits is executing with a reference to the object accounts[1] returned by the getAccount() method for the transaction, the execution of the block carrying out debits cannot be executed for the same object, but it could be executed for a different account.

The object in a synchronized code block acts rather like a baton in a relay race that serves to synchronize the runners in the team. Only the runner with the baton is allowed to run. The next runner in the team can only run once they get hold of the baton. Of course, in any race there will be several different batons so you can have several sets of runners. In the same way, you can specify several different sets of synchronized code blocks in a class, each controlled by a different object. It is important to realize that code blocks that are synchronized with respect to a particular object don't have to be in the same class. They can be anywhere in your program where the appropriate object can be specified.

Note how we had to move the code to access and restore the account balance inside both synchronized blocks. If we hadn't done this, accessing or restoring the account balance could occur while a synchronized block was executing. This could obviously cause confusion since a balance could be restored by a debit transaction after the balance had been retrieved for a credit transaction. This would cause the effect of the debit to be wiped out.

If you want to verify that we really are overlapping these operations in this example, you can add output statements to the beginning and end of each method in the class Bank. Outputting the type of operation, the amount, and whether it is the start or end of the transaction will be sufficient to identify them. For example, you could modify the doTransaction() method in the Bank class to:

// Perform a transaction
public void doTransaction(Transaction transaction) {
  switch(transaction.getTransactionType()) {
    case Transaction.CREDIT:
      synchronized(transaction.getAccount()) {
        System.out.println("Start credit of " +
                transaction.getAccount() + " amount: " + 
                transaction.getAmount());

        // code to process credit...
        System.out.println("  End credit of " +
                transaction.getAccount() + " amount: " + 
                transaction.getAmount());
        break;
      }

    case Transaction.DEBIT:
      synchronized(transaction.getAccount()) {
        System.out.println("Start debit of " +
                transaction.getAccount() + " amount: " + 
                transaction.getAmount());
        // code to process debit...
        System.out.println("  End debit of " +
                transaction.getAccount() + " amount: " + 
                transaction.getAmount());
        break;
      }

    default:                                       // We should never get here
      System.out.println("Invalid transaction");
      System.exit(1);
  }
}

This will produce quite a lot of output, but you can always comment it out when you don't need it. You should be able to see how a transaction for an account that is currently being worked on is always delayed until the previous operation on the account is completed. You will also see from the output that operations on different accounts do overlap. Here's a sample of what I got:

Start credit of A//C No. 2 : $800 amount: 74
  End credit of A//C No. 2 : $874 amount: 74
Start debit of A//C No. 2 : $874 amount: 52
Start credit of A//C No. 1 : $500 amount: 51
  End debit of A//C No. 2 : $822 amount: 52
  End credit of A//C No. 1 : $551 amount: 51
Start debit of A//C No. 2 : $822 amount: 38
  End debit of A//C No. 2 : $784 amount: 38
Start credit of A//C No. 2 : $784 amount: 74
  End credit of A//C No. 2 : $858 amount: 74
Start debit of A//C No. 1 : $551 amount: 58
Start credit of A//C No. 2 : $858 amount: 53
  End debit of A//C No. 1 : $493 amount: 58
...

You can see from the third and fourth lines here that a credit for account 1 starts before the preceding debit for account 2 is complete, so the operations are overlapped. If you want to force overlapping debits and credits on the same account, you can comment out the calculation of the value for select for the debit operation in the for loop in main(). This modification is shown shaded:

// Generate a random account index for debit operation
// select = rand.nextInt(accounts.length);
totalDebits[select] += amount;              // Keep total debit tally

This will make the debit transaction apply to the same account as the previous credit, so the transactions will always be contending for the same account.

Of course, this is not the only way of getting the operations to overlap. Another approach would be to equip accounts with methods to handle their own credit and debit transactions, and declare these as synchronized methods.

While testing that you have synchronization right is relatively easy in our example, in general it is extremely difficult to be sure you have tested a program that uses threads adequately. Getting the design right first is essential, and there is no substitute for careful design in programs that have multiple threads (or indeed any real time program that has interrupt handlers). You can never be sure that a real world program is 100% correct, only that it works correctly most of the time!

Deadlocks

Since you can synchronize code blocks for a particular object virtually anywhere in your program, there's potential for a particularly nasty kind of bug called a deadlock. This involves a mutual interdependence between two threads. One way this arises is when one thread executes some code synchronized on a given object, theObject say, and then needs to execute another method that contains code synchronized on another object, theOtherObject say. Before this occurs though, a second thread executes some code synchronized to theOtherObject, and needs to execute a method containing code synchronized to the first object, theObject. This situation is illustrated here:

Click To expand

The sequence of events is as follows:

  • thread1 starts first, and synchronizes on theObject. This prevents any methods for theObject being called by any other thread.

  • thread1 then calls sleep() so thread2 can start.

  • thread2 starts and synchronizes on theOtherObject. This prevents any methods for theOtherObject being called by any other thread.

  • thread2 then calls sleep()allowing thread1 another go.

  • thread1 wakes up and tries to call method2() for theOtherObject, but it can't until the code block in thread2 that is synchronized on theOtherObject completes execution.

  • thread2 gets another go because thread1 can't proceed, and tries to call method1() for theObject. This can't proceed until the code block in thread1 that is synchronized on theObject completes execution.

Neither thread has any possibility of continuing – they are deadlocked. Finding and fixing this sort of problem can be very difficult, particularly if your program is complicated and has other threads that will continue to execute.

You can create a trivial deadlock in the last example by making the for loop in main() synchronized on one of the accounts. For example:

synchronized(accounts[1]) {
  for(int i = 1; i <= transactionCount; i++) {
    // code for generating transactions etc...
  }
}

A deadlock occurs as soon as a transaction for accounts[1] arises because the doTransaction() method in the theBank object that is called by a Clerk object to handle the transaction will be synchronized to the same object and can't execute until the loop ends. Of course, the loop can't continue until the method in the theBank object terminates so the program hangs.

In general, ensuring that your program has no potential deadlocks is extremely difficult. If you intend to do a significant amount of programming using threads, you will need to study the subject in much more depth than we can deal with here. A good book on the subject is Concurrent Programming in Java: Design Principles and Patterns written by Doug Lea (ISBN 0-201-69581-2).

Communicating between Threads

We've seen how we can lock methods or code blocks using synchronization to avoid the problems that uncontrolled thread execution can cause. While this gives us a degree of control, we're still introducing inefficiencies into the program. In the last example, there were several occasions where we used a loop to wait for a clerk thread to complete an operation before the current thread could sensibly continue. For example, we couldn't pass a transaction to a Clerk object while that object was still busy with the previous transaction. Our solution to this was to use a while loop to test the busy status of the Clerk object from time to time and call the sleep() method in between. But there's a much better way.

The Object class defines the methods wait(), notify(), and notifyAll(), which you can use to provide a more efficient way of dealing with this kind of situation. Since all classes are derived from Object, all classes inherit these methods. You can only call these methods from within a synchronized method, or from within a synchronized code block, and an exception of type IllegalMonitorStateException will be thrown if you call them from somewhere. The functions that these methods perform are:

Method

Description

wait()

There are three overloaded versions of this method.

This version suspends the current thread until the notify() or notifyAll() method is called for the object to which the wait() method belongs. Note that when any version of wait() is called, the thread releases the synchronization lock it has on the object, so any other method or code block synchronized on the same object can execute. As well as enabling notify() or notifyAll() to be called by another thread, this also allows another thread to call wait() for the same object.

Since all versions of the wait() method can throw an InterruptedException, you must call it in a try block with a catch block for this exception, or at least indicate that the method calling it throws this exception.

wait(long timeout)

This version suspends the current thread until the number of milliseconds specified by the argument has expired, or until the notify() or notifyAll() method for the object to which the wait() method belongs is called, if that occurs sooner.

wait(long timeout, int nanos)

This version works in the same way as the previous version, except the time interval is specified by two arguments, the first in milliseconds, and the second in nanoseconds.

notify()

This will restart a thread that has called the wait() method for the object to which the notify() method belongs. If several threads have called wait() for the object, you have no control over which thread is notified, in which case it is better to use notifyAll(). If no threads are waiting, the method does nothing.

notifyAll()

This will restart all threads that have called wait() for the object to which the notifyAll() method belongs.

The basic idea of the wait() and notify() methods is that they provide a way for methods or code blocks that are synchronized on a particular object to communicate. One block can call wait() to suspend its operation until some other method or code block synchronized on the same object changes it in some way, and calls notify() to signal that the change is complete. A thread will typically call wait() because some particular property of the object it is synchronized on is not set, or some condition is not fulfilled, and this is dependent on action by another thread. Perhaps the simplest situation is where a resource is busy because it is being modified by another thread, but you are by no means limited to that.

The major difference between calling sleep() and calling wait() is that wait()releases any objects on which the current thread has a lock, whereas sleep() does not. It is essential that wait() should work this way, otherwise there would be no way for another thread to change things so that the condition required by the current thread is met.

Thus the typical use of wait() is:

synchronized(anObject) {
  while(condition-not-met)
    anObject.wait();
  // Condition is met so continue...
}

Here the thread will suspend operation when the wait() method is called until some other thread synchronized on the same object calls notify() (or more typically notifyAll()). This allows the while loop to continue and check the condition again. Of course, it may still not be met, in which case the wait() method will be called again so another thread can operate on anObject. You can see from this that wait() is not just for getting access to an object. It is intended to allow other threads access until some condition has been met. You could even arrange that a thread would not continue until a given number of other threads had called notify() on the object to ensure that a minimum number of operations had been carried out.

It is generally better to use notifyAll() rather than notify() when you have more than two threads synchronized on an object. If you call notify() when there are two or more other threads suspended having called wait(), only one of the threads will be started, but you have no control over which it is. This opens the possibility that the thread that is started calls wait() again because the condition it requires is not fulfilled. This will leave all the threads waiting for each other, with no possibility of continuing.

Although the action of each of these methods is quite simple, applying them can become very complex. You have the potential for multiple threads to be interacting through several objects with synchronized methods and code blocks. We'll just explore the basics by seeing how we can use wait() and notifyAll() to get rid of a couple of the while loops we had in the last example.

Using wait() and notifyAll() in the Bank Program

In the for loop in main() that generates the transactions and passes them to the Clerk objects, we have two while loops that call the isBusy() method for a Clerk object. These were needed so that we didn't pass a transaction to a clerk while the clerk was still busy. By altering the Clerk class, so that it can use wait()and notifyAll(), we can eliminate the need for these.

Try It Out – Slimming Down the Transactions Loop

We want to make the doTransaction() method in the Clerk class conscious of the state of the inTray for the current object. If it is not null, we want the method to wait until it becomes so. To use wait() the block or method must be synchronized on an object – in this case the Clerk object since inTray is what we are interested in. We can do this by making the method synchronized:

public class Clerk implements Runnable {
  private Bank theBank;               // The employer - an electronic marvel
  private Transaction inTray;         // The in-tray holding a transaction

  // Constructor
  public Clerk(Bank theBank) {
    this.theBank = theBank;           // Who the clerk works for
    inTray = null;                    // No transaction initially
  }

  // Receive a transaction
  synchronized public void doTransaction(Transaction transaction) {
    while(inTray != null) {
      try {
        wait();

      } catch(InterruptedException e) {
        System.out.println(e);
      }
    }
    inTray = transaction;
    notifyAll();
  }

  // Rest of the class as before...
}

When inTray is null, the transaction is stored and the notifyAll() method is called to notify other threads waiting on a change to this Clerk object. If inTray is not null, this method waits until some other thread calls notifyAll() to signal a change to the Clerk object. We now need to consider where the inTray field is going to be modified elsewhere. The answer is in the run() method for the Clerk class, of course, so we need to change that too:

  synchronized public void run() {
    while(true) {
      while(inTray == null)           // No transaction waiting?
        try {
          wait();                     // Then take a break until there is

        } catch(InterruptedException e) {
          System.out.println(e);
        }

      theBank.doTransaction(inTray);
      inTray = null;                // In-tray is empty
      notifyAll();                  // Notify other threads locked on this clerk
    }
  }

  // Rest of the class as before...
}

Just to make it clear which methods are in what threads, the situation in our program is illustrated below.

Click To expand

Here the run() method is synchronized on the Clerk object that contains it, and the method waits if inTray is null. Eventually the doTransaction() method for the current object should store a transaction in inTray, and then notify the thread that is waiting that it should continue.

It may seem odd having two methods in the same object synchronized on one and the same object that owns them, but remember that the run() and doTransaction() methods for a particular Clerk object are in separate threads.

The transaction processing method for the bank can be in both of the clerk threads, whereas the methods that hand over a transaction to a clerk are in the main thread. The diagram also shows which code is synchronized on what objects.

We can now modify the code in the for loop in main()to pass the transactions directly to the clerks. Except for deleting the two while loops that wait until the clerks are free, the code is exactly as before:

// Create transactions randomly distributed between the accounts
for(int i = 1; i <= transactionCount; i++) {
  // Generate a random account index for credit operation

  select = rand.nextInt(accounts.length);
  amount = 50 + rand.nextInt(26);            // Generate amount of $50 to $75
  transaction = new Transaction(accounts[select],    // Account
                               Transaction.CREDIT, // Credit transaction
                                amount);           //  of amount
  totalCredits[select] += amount;            // Keep total credit tally

  clerk1.doTransaction(transaction);         // Now do the credit

  // Generate a random account index for debit operation
  select = rand.nextInt(accounts.length);
  amount = 30 + rand.nextInt(31);            // Generate amount of $30 to $60
  transaction = new Transaction(accounts[select],   // Account
                                Transaction.DEBIT,  // Debit transaction
                                amount);            //  of amount
  totalDebits[select] += amount;             // Keep total debit tally

  clerk2.doTransaction(transaction);         // Now do the debit
}

We have just deleted the loop blocks that were waiting until a clerk became free. This makes our code a lot shorter.

With a small change to the isBusy() method in the Clerk class, we can also eliminate the need for the while loop before we output the results in main():

synchronized public void isBusy() {
  while(inTray != null) {           // Is this object busy?
    try {
      wait();                       // Yes, so wait for notify call

    } catch(InterruptedException e) {
      System.out.println(e);
    }
  return;                           // It is free now
  }
}

Now the isBusy() method will only return when the clerk object has no transaction waiting or in progress, so no return value is necessary. The while loop in main() before the final output statements can be replaced by:

// Wait until both clerks are done
clerk1.isBusy();
clerk2.isBusy();

How It Works

The doTransaction() method for a Clerk object calls the wait() method if the inTray field contains a reference to a transaction object, as this means the Clerk object is still processing a credit or a debit. This will result in the current thread (which is the main thread) being suspended until the notifyAll() method is called by this object's run() method to indicate a change to the clerk.

Because the run()method is also synchronized on the Clerk object, it too can call wait() in this case, if the inTray contains null, since this indicates that there is no transaction waiting for the clerk to expedite. A call to the doTransaction() method for the Clerk object will result in a transaction being stored in inTray, and the notifyAll() call will wake up the run() method to continue execution.

Because we've declared the isBusy()method as synchronized, we can call the wait() method to suspend the current thread if transactions are still being processed. Since we don't return from the method until the outstanding transaction is complete, we have no need of a boolean return value.

Previous Next
JavaScript Editor Java Tutorials Free JavaScript Editor