TopConcurrencyConcurrency & Interference

Concurrency & Interference

An important class of problems can arise with concurrency when there are several threads that might try to update the same variable at the same time.

Consider an example of a bank with two ATM machines which can be used to deposit and withdraw money. The following demo is a program that simulates this situation.  
ATM2

One of the ATMs will repeatedly withdraw $100 from the account while the other will repeatedly deposit $100 in the account (see the difference in parameters in the constructors for the ATM's). When the user pushes the button, the actionPerformed method repeats the construction and execution of the ATM objects.

The main items of interest here are the getBalance and changeBalance methods.

The run method repeatedly deducts change from the account by executing account.changeBalance(change) to update the balance.

The final balance in the account should be $1000, the same amount started with, as one of the ATM objects withdraws $100 one thousand times, while the other deposits $100 the same number of times. If you run this code enough, however, you will discover that the answer does not always turn out to be $1000! In fact, different balances result nearly every time you run the program. What is causing the problems? Look at the program to see if you can determine what is going wrong before reading further.

The error occurs because two different threads (objects of type ATM, which is a subclass of Thread) are updating the same variable, balance. Each calls the changeBalance method repeatedly. However, it can happen that both ATM's get the balance before either of them has the opportunity to update the balance inside the changeBalance method.

For example, suppose ATM1 gets the balance of $1000, while ATM2 "simultaneously" gets the balance of $1000 (they aren't actually happening simultaneously because there is only one processor, but for our purposes it can be helpful to think that way). Now ATM1 adds $100 to the balance and updates the balance to $1100. ATM2 then removes $100 from the balance that it originally got ($1000) and updates the balance to $900. Thus if the interleaving of operations of ATM1 and ATM2 are such that both get balances before either registers the new balance, the final balance will not reflect one of the two operations.

Clearly this is a problem, yet we would like to have the operations of the two ATM's interleaved. (We could just run ATM1 to conclusion before starting up ATM2, but this does not model the usage of ATM's properly.)

We would like to ensure that if ATM1 queries the balance with the intent to change it and set a new balance, that ATM2 does not read the original balance. It is when both read the old balance and both update that one of the transactions is lost.

To be absolutely safe, we must ensure that only one thread at a time can execute the method changeBalance. We can do this in Java by using the keyword synchronized.

If we attach the keyword synchronized to methods in a class, then Java will ensure that only one thread at a time will be executing any of those methods. For example we can label both getBalance and changeBalance as synchronized:

    // return the current balance of bank account
    public synchronized int getBalance() {
        return balance;
    }
    
    // update the balance of bank account 
    public synchronized void changeBalance(int change) {
        int newBalance = balance + change;
        display.setText ("" + balance);
        balance = newBalance;    }

ATM3

Now if a thread associated with one ATM object is executing either of these methods, then no other thread can execute either of the methods. For example, if ATM1 is executing changeBalance, then ATM2 will not be allowed to execute either changeBalance or getBalance. Instead it will wait until ATM1 has finished executing that method and then execute the desired method. (The operating system is given the responsibility of scheduling the threads' access to the processor.)

A thread executing either changeBalance or getBalance has no impact on another thread's attempts at executing any of the non-synchronized methods of the program. Thus the user-interface thread can be executing the actionPerformed method while ATM1 is executing changeBalance.

Because of the use of synchronized, neither thread can interfere with the other, ensuring that the final answer is the correct one. However, there is one disadvantage of using synchronized methods - they cut down on the amount of concurrency in the system. This may slow down the execution of the program, as one thread may be waiting for an operation to complete (e.g., a write to the screen or a read from a file), while another might be ready to do something. The second thread may be ready to use the processor, but if it is ready to execute a synchronized method and the other thread is executing a synchronized method of the same object, then it may be blocked from executing.

This example may seem a bit contrived - we carefully made sure to insert an operation that updated the total on the screen between getting the old balance and setting the new one. Any sort of operation that affects the outside world (reading or writing from a stream, displaying something on the screen), will present the opportunity for another thread to execute, allowing the kind of interference that we have seen here. [Changing the program so that the setText message is sent after the balance is updated will likely result in the "correct" answer always appearing on the screen.] However, the interference could happen in each of our cases (except the one with the synchronized modifiers) even without the careful attempts to increase the chances. Have any of you ever had some big program, even maybe a commercial program, crash in an unexpected and non-reproducible manner? Perhaps a web browser or even Windows itself? There's a pretty good chance that a lot of those kinds of crashes are the result of concurrency not being dealt with carefully enough. Most of the time, things are fine - but once in a while just the right combination of things is happening and there you go. Crash and burn.

There are many other complications involved in the use of concurrency - too many to go into detail here. Concurrency can be quite challenging, and inattention to details may result in programs that don't work as expected. Most of the programs that we have had you write that involve active objects have been designed so that no interference is possible. However, the Simon program did provide the opportunity for interference. It is possible to avoid this possibility by using synchronized methods. We urge you to worry about the possibilities of this happening when you use active objects or threads. More advanced Computer Science courses study concurrency in much more detail, including other problems that may arise and the techniques to deal with them.

The following picture describes the possible states of a thread in Java, and how it moves from one state to another:


TopConcurrencyConcurrency & Interference