Synchronization Core Java

In most practical multithreaded applications, two or more threads need to share accessto the same data. What happens if two threads have access to the same object and eachcalls a method that modifies the state of the object? As you might imagine, the threadscan step on each other’s toes. Depending on the order in which the data were accessed,corrupted objects can result. Such a situation is often called a race condition.

An Example of a Race Condition

To avoid corruption of shared data by multiple threads, you must learn how to synchronizethe access. In this section, you’ll see what happens if you do not use synchronization. In the next section, you’ll see how to synchronize data access.

In the next test program, we simulate a bank with a number of accounts. We randomlygenerate transactions that move money between these accounts. Each account has onethread. Each transaction moves a random amount of money from the account servicedby the thread to another random account.

The simulation code is straightforward. We have the class Bank with the method transfer.This method transfers some amount of money from one account to another. (We don’tyet worry about negative account balances.) Here is the code for the transfer method ofthe Bank class.

Here is the code for the TransferRunnable class. Its run method keeps moving money out ofa fixed bank account. In each iteration, the run method picks a random target accountand a random amount, calls transfer on the bank object, and then sleeps.class TransferRunnable implements Runnable

When this simulation runs, we do not know how much money is in any one bankaccount at any time. But we do know that the total amount of money in all the accountsshould remain unchanged because all we do is move money from one account toanother.

At the end of each transaction, the transfer method recomputes the total and prints it. This program never finishes. Just press CTRL+C to kill the program. Here is a typical printout:

As you can see, something is very wrong. For a few transactions, the bank balanceremains at $100,000, which is the correct total for 100 accounts of $1,000 each. Butafter some time, the balance changes slightly. When you run this program, youmay find that errors happen quickly or it may take a very long time for the balanceto become corrupted. This situation does not inspire confidence, and you wouldprobably not want to deposit your hard-earned money in this bank.

The Race Condition Explained

In the previous section, we ran a program in which several threads updated bankaccount balances. After a while, errors crept in and some amount of money waseither lost or spontaneously created. This problem occurs when two threads aresimultaneously trying to update an account. Suppose two threads simultaneouslycarry out the instruction

The problem is that these are not atomic operations. The instruction might be processedas follows:

  1. Load accounts[to] into a register.
  2. Add amount.
  3. Move the result back to accounts[to].

Now, suppose the first thread executes Steps 1 and 2, and then it is preempted. Supposethe second thread awakens and updates the same entry in the account array.

Then, the first thread awakens and completes its Step 3. That action wipes out the modification of the other thread. As a result, the total is nolonger correct.

Simultaneous access by two threads

Simultaneous access by two threads

Our test program detects this corruption. (Of course, there is a slight chance of falsealarms if the thread is interrupted as it is performing the tests!)

NOTE: You can actually peek at the virtual machine bytecodes that execute each statementin our class. Run the command

What these codes mean does not matter. The point is that the increment command is madeup of several instructions, and the thread executing them can be interrupted at the point ofany instruction. What is the chance of this corruption occurring? We boosted the chance of observingthe problem by interleaving the print statements with the statements that update thebalance.

If you omit the print statements, the risk of corruption is quite a bit lower because eachthread does so little work before going to sleep again, and it is unlikely that the schedulerwill preempt it in the middle of the computation. However, the risk of corruptiondoes not completely go away. If you run lots of threads on a heavily loaded machine,then the program will still fail even after you have eliminated the print statements. Thefailure may take a few minutes or hours or days to occur. Frankly, there are few thingsworse in the life of a programmer than an error that only manifests itself once every fewdays.

The real problem is that the work of the transfer method can be interrupted in themiddle. If we could ensure that the method runs to completion before the threadloses control, then the state of the bank account object would never be corrupted.

Lock Objects

Starting with Java SE 5.0, there are two mechanisms for protecting a code blockfrom concurrent access. The Java language provides a synchronized keyword for thispurpose, and Java SE 5.0 introduced the ReentrantLock class. The synchronized keywordautomatically provides a lock as well as an associated “condition,” which makes itpowerful and convenient for most cases that require explicit locking. However, webelieve that it is easier to understand the synchronized keyword after you have seenlocks and conditions in isolation. The java.util.concurrent framework provides separateclasses for these fundamental mechanisms.

The basic outline for protecting a code block with a ReentrantLock is:

This construct guarantees that only one thread at a time can enter the critical section. As soon as one thread locks the lock object, no other thread can get past the lock statement. When other threads call lock, they are deactivated until the first thread unlocks the lock object.

CAUTION: It is critically important that the unlock operation is enclosed in a finally clause. If the code in the critical section throws an exception, the lock must be unlocked. Otherwise, the other threads will be blocked forever.

Let us use a lock to protect the transfer method of the Bank class.

Suppose one thread calls transfer and gets preempted before it is done. Suppose a second thread also calls transfer. The second thread cannot acquire the lock and is blocked in the call to the lock method. It is deactivated and must wait for the first thread to finish executing the transfer method. When the first thread unlocks the lock, then the second thread can proceed . Try it out. Add the locking code to the transfer method and run the program again. You can run it forever, and the bank balance will not become corrupted.

Comparison of unsynchronized and synchronized threads

Comparison of unsynchronized and synchronized threads

Note that each Bank object has its own Reentrant Lock object. If two threads try to access the same Bank object, then the lock serves to serialize the access. However, if two threads access different Bank objects, then each thread acquires a different lock and neither thread is blocked. This is as it should be, because the threads cannot interfere with another when they manipulate different Bank instances.

The lock is called reentrant because a thread can repeatedly acquire a lock that it already owns. The lock keeps a hold count that keeps track of the nested calls to the lock method. The thread has to call unlock for every call to lock in order to relinquish the lock. Because of this feature, code that is protected by a lock can call another method that uses the same locks.

For example, the transfer method calls the getTotalBalance method, which also locks thebankLock object, which now has a hold count of 2. When the get Total Balance method exits, the hold count is back to 1. When the transfer method exits, the hold count is 0, and the thread relinquishes the lock.

In general, you will want to protect blocks of code that update or inspect a shared object. You are then assured that these operations run to completion before another thread can use the same object.

CAUTION: You need to be careful that code in a critical section is not bypassed through the throwing of an exception. If an exception is thrown before the end of the section, then the finally clause will relinquish the lock but the object may be in a damaged state.

java.util.concurrent.locks.Lock

  • void lock()
    acquires this lock; blocks if the lock is currently owned by another thread.
  • void unlock()
    releases this lock.

java.util.concurrent.locks.ReentrantLock

  • ReentrantLock()
    constructs a reentrant lock that can be used to protect a critical section.
  • ReentrantLock(boolean fair)
    constructs a lock with the given fairness policy. A fair lock favors the thread that has been waiting for the longest time. However, this fairness guarantee can be asignificant drag on performance. Therefore, by default, locks are not required tobe fair.

CAUTION: It sounds nicer to be fair, but fair locks are a lot slower than regular locks. You should only enable fair locking if you truly know what you are doing and have a specific reason why fairness is essential for your problem. Even if you use a fair lock, you have no guarantee that the thread scheduler is fair. If the thread scheduler chooses to neglect a thread that has been waiting a long time for the lock, then it doesn’t get the chance to be treated fairly by the lock.

Condition Objects

Often, a thread enters a critical section, only to discover that it can’t proceed until a condition is fulfilled. You use a condition object to manage threads that have acquired a lock but cannot do useful work. In this section, we introduce the implementation of condition objects in the Java library. (For historical reasons, condition objects are often called condition variables.)

Let us refine our simulation of the bank. We do not want to transfer money out of an account that does not have the funds to cover the transfer. Note that we cannot use code like

It is entirely possible that the current thread will be deactivated between the successful outcome of the test and the call to transfer.

By the time the thread is running again, the account balance may have fallen below the withdrawal amount. You must make sure that no other thread can modify the balance between the test and the transfer action. You do so by protecting both the test and the transfer action with a lock:

Now, what do we do when there is not enough money in the account? We wait until some other thread has added funds. But this thread has just gained exclusive access to the bankLock, so no other thread has a chance to make a deposit. This is where condition objects come in.

A lock object can have one or more associated condition objects. You obtain a condition object with the newCondition method. It is customary to give each condition object a name that evokes the condition that it represents. For example, here we set up a conditionobject to represent the “sufficient funds” condition.

If the transfer method finds that sufficient funds are not available, it calls

The current thread is now deactivated and gives up the lock. This lets in another thread that can, we hope, increase the account balance.

There is an essential difference between a thread that is waiting to acquire a lock and a thread that has called await. Once a thread calls the await method, it enters a wait set for that condition. The thread is not made runnable when the lock is available. Instead, it stays deactivated until another thread has called the signal All method on the same condition. When another thread transfers money, then it should call

This call reactivates all threads that are waiting for the condition. When the threads are removed from the wait set, they are again runnable and the scheduler will eventually activate them again. At that time, they will attempt to reenter the object. As soon as the lock is available, one of them will acquire the lock and continue where it left off, returning from the call to await.

At this time, the thread should test the condition again. There is no guarantee that the condition is now fulfilled—the signalAll method merely signals to the waiting threads that it may be fulfilled at this time and that it is worth checking for the condition again.

NOTE: In general, a call to await should be inside a loop of the form while (!(ok to proceed))condition.await();

It is crucially important that some other thread calls the signalAll method eventually. When a thread calls await, it has no way of reactivating itself. It puts its faith in the other threads. If none of them bother to reactivate the waiting thread, it will never run again.

This can lead to unpleasant deadlock situations. If all other threads are blocked and the last active thread calls await without unblocking one of the others, then it also blocks. No thread is left to unblock the others, and the program hangs.

When should you call signalAll? The rule of thumb is to call signalAll whenever the state of an object changes in a way that might be advantageous to waiting threads. For example, whenever an account balance changes, the waiting threads should be given anotherchance to inspect the balance. In our example, we call signalAll when we have finished the funds transfer.

Notethat the call to signalAll does not immediately activate a waiting thread. It only unblocks the waiting threads so that they can compete for entry into the object after the current thread has exited the synchronized method. Another method, signal, unblocks only a single thread from the wait set, chosen at random.

That is more efficient than unblocking all threads, but there is a danger. If the randomlychosen thread finds that it still cannot proceed, then it becomes blocked again. If no other thread calls signal again, then the system deadlocks.

CAUTION: A thread can only call await, signalAll, or signal on a condition when it owns the lock of the condition.

The total balance stays at $100,000 forever. No account ever has a negative balance. (Again, you need to press CTRL+C to terminate the program.) You may also notice that the program runs a bit slower—this is the price you pay for the added bookkeeping involved in the synchronization mechanism. In practice, using conditions correctly can be quite challenging.

java.util.concurrent.locks.Lock

  • Condition newCondition()
    returns a condition object that is associated with this lock.

java.util.concurrent.locks.Condition 5.0

  • void await()
  • puts this thread on the wait set for this condition.
  • void signalAll()
    unblocks all threads in the wait set for this condition.
  • void signal()
    unblocks one randomly selected thread in the wait set for this condition.

The synchronized Keyword

  • In the preceding sections, you saw how to use Lock and Condition objects. Before going any further, let us summarize the key points about locks and conditions:
  • A lock protects sections of code, allowing only one thread to execute the code at a time.
  • A lock manages threads that are trying to enter a protected code segment.
  • A lock can have one or more associated condition objects.
  • Each condition object manages threads that have entered a protected code section but that cannot proceed.

The Lock and Condition interfaces were added to Java SE 5.0 to give programmers a high degree of control over locking. However, in most situations, you don’t need that control, and you can use a mechanism that is built into the Java language. Ever since version 1.0, every object in Java has an intrinsic lock. If a method is declared with the synchronized keyword, then the object’s lock protects the entire method. That is, to call the method, a thread must acquire the intrinsic object lock.

In other words,

For example, instead of using an explicit lock, we can simply declare the transfer method of the Bank class as synchronized.

The intrinsic object lock has a single associated condition. The wait method adds a thread to the wait set, and the notifyAll/notify methods unblock waiting threads. In other words, calling wait or notifyAll is the equivalent of

NOTE: The wait, notifyAll, and notify methods are final methods of the Object class. The Condition methods had to be named await, signalAll, and signal so that they don’t conflict with those methods.

For example, you can implement the Bank class in Java like this:

As you can see, using the synchronized keyword yields code that is much more concise. Of course, to understand this code, you have to know that each object has an intrinsic lock, and that the lock has an intrinsic condition. The lock manages the threads that try to enter a synchronized method. The condition manages the threads that have called wait.

TIP: Synchronized methods are relatively straightforward. However, beginners often struggle with conditions.

It is also legal to declare static methods as synchronized. If such a method is called, it acquires the intrinsic lock of the associated class object. For example, if the Bank class has a static synchronized method, then the lock of the Bank.class object is locked when it is called. As a result, no other thread can call this or any other synchronized static method of the same class.

The intrinsic locks and conditions have some limitations. Among them:

  • You cannot interrupt a thread that is trying to acquire a lock.
  • You cannot specify a timeout when trying to acquire a lock.
  • Having a single condition per lock can be inefficient.
    What should you use in your code—Lock and Condition objects or synchronized methods?
    Here is our recommendation:
  • It is best to use neither Lock/Condition nor the synchronized keyword. In many situations, you can use one of the mechanisms of the java.util.concurrent package that do all the locking for you. For example, , you will see how to use a blocking queue to synchronize threads that work on a common task.
  • If the synchronized keyword works for your situation, by all means, use it. You write less code and have less room for error. Listing belowshows the bank example, implemented with synchronized methods.
  • Use Lock/Condition if you specifically need the additional power that these constructs give you.

UnsynchBankTest.java

java.lang.Object

  • void notifyAll()
    unblocks the threads that called wait on this object. This method can only be called from within a synchronized method or block. The method throws an IllegalMonitorStateException if the current thread is not the owner of the object’s lock.
  • void notify()
    unblocks one randomly selected thread among the threads that called wait on this object. This method can only be called from within a synchronized method or block. The method throws an IllegalMonitorStateException if the current thread is not the owner of the object’s lock.
  • void wait()
    causes a thread to wait until it is notified. This method can only be called from within a synchronized method. It throws an IllegalMonitorStateException if the current thread is not the owner of the object’s lock.
  • void wait(long millis)
  • void wait(long millis, int nanos)
    causes a thread to wait until it is notified or until the specified amount of time has passed. These methods can only be called from within a synchronized method. They throw an IllegalMonitorStateException if the current thread is not the owner of the object’s lock.

Synchronized Blocks

As we just discussed, every Java object has a lock. A thread can acquire the lock by calling a synchronized method. There is a second mechanism for acquiring the lock, by entering a synchronized block. When a thread enters a block of the form synchronized (obj) // this is the syntax for a synchronized block

Here, the lock object is created only to use the lock that every Java object possesses. Sometimes, programmers use the lock of an object to implement additional atomic operations, a practice known as client-side locking. Consider, for example, the Vector class, a list whose methods are synchronized. Now suppose we stored our bank balances in a

Vector<Double>. Here is a naive implementation of a transfer method:

The get and set methods of the Vector class are synchronized, but that doesn’t help us. It is entirely possible for a thread to be preempted in the transfer method after the first call to get has been completed. Another thread may then store a different value into the same position. However, we can hijack the lock:

This approach works, but it is entirely dependent on the fact that the Vector class uses the intrinsic lock for all of its mutator methods. However, is this really a fact? The documentation of the Vector class makes no such promise. You have to carefully study the source code and hope that future versions do not introduce unsynchronized mutators. As you can see, client-side locking is very fragile and not generally recommended.

The Monitor Concept

Locks and conditions are powerful tools for thread synchronization, but they are not very object oriented. For many years, researchers have looked for ways to make multithreading safe without forcing programmers to think about explicit locks. One of the most successful solutions is the monitor concept that was pioneered by Per Brinch Hansen and Tony Hoare in the 1970s. In the terminology of Java, a monitor has these properties:

  • A monitor is a class with only private fields.
  • Each object of that class has an associated lock.
  • All methods are locked by that lock. In other words, if a client calls obj.method(), then the lock for obj is automatically acquired at the beginning of the method call and relinquished when the method returns. Because all fields are private, this arrangement ensures that no thread can access the fields while another thread manipulates them.
  • The lock can have any number of associated conditions.

Earlier versions of monitors had a single condition, with a rather elegant syntax. You can simply call await accounts[from] >= balance without using an explicit condition variable. However, research showed that indiscriminate retesting of conditions can be inefficient. This problem is solved with explicit condition variables, each managing a separate set of threads.

The Java designers loosely adapted the monitor concept. Every object in Java has an intrinsic lock and an intrinsic condition. If a method is declared with the synchronized keyword, then it acts like a monitor method. The condition variable is accessed by calling wait/notifyAll/notify.

However, a Java object differs from a monitor in three important ways, compromising thread safety:

  • Fields are not required to be private.
  • Methods are not required to be synchronized.
  • The intrinsic lock is available to clients.

This disrespect for security enraged Per Brinch Hansen. In a scathing review of the multithreading primitives in Java, he wrote: “It is astounding to me that Java’s insecure parallelism is taken seriously by the programming community, a quarter of a century after the invention of monitors and Concurrent Pascal. It has no merit.” [Java’s Insecure Parallelism,

Volatile Fields

Sometimes, it seems excessive to pay the cost of synchronization just to read or write an instance field or two. After all, what can go wrong? Unfortunately, with modern processors and compilers, there is plenty of room for error:

  • Computers with multiple processors can temporarily hold memory values in registers or local memory caches. As a consequence, threads running in different processors may see different values for the same memory location!
  • Compilers can reorder instructions for maximum throughput. Compilers won’t choose an ordering that changes the meaning of the code, but they make the assumption that memory values are only changed when there are explicit instructions in the code. However, a memory value can be changed by another thread!

If you use locks to protect code that can be accessed by multiple threads, then you won’t have these problems. Compilers are required to respect locks by flushing local caches as necessary and not inappropriately reordering instructions. Much of the specification is highly complex and technical,but the document also contains a number of clearly explained examples.

NOTE: Brian Goetz coined the following “synchronization motto”: “If you write a variable which may next be read by another thread, or you read a variable which may have last been written by another thread, you must use synchronization.”

The volatile keyword offers a lock-free mechanism for synchronizing access to an instance field. If you declare a field as volatile, then the compiler and the virtual machine take into account that the field may be concurrently updated by another thread.

For example, suppose an object has a boolean flag done that is set by one thread and queried by another thread. As we already discussed, you can use a lock:

Perhaps it is not a good idea to use the intrinsic object lock. The isDone and setDone methods can block if another thread has locked the object. If that is a concern, one can use a separate Lock just for this variable. But this is getting to be a lot of trouble.

In this case, it is reasonable to declare the field as volatile:

CAUTION: Volatile variables do not provide any atomicity. For example, the method public void flipDone() { done = !done; } // not atomicis not guaranteed to flip the value of the field.

In this very simple case, there is a third possibility, to use an AtomicBoolean. This class has methods get and set that are guaranteed to be atomic (as if they were synchronized). The implementation uses efficient machine-level instructions that guarantee atomicity without using locks. There are a number of wrapper classes in the java.util.concurrent.atomic

package for atomic integers, floating point numbers, arrays, and so on. These classes are intended for systems programmers who produce concurrency utilities, not for the application programmer.

In summary, concurrent access to a field is safe in these three conditions:

  • The field is final, and it is accessed after the constructor has completed.
  • Every access to the field is protected by a common lock.
  • The field is volatile.

NOTE: Prior to Java SE 5.0, the semantics of volatile were rather permissive. The language designers attempted to give implementors leeway in optimizing the performance of code that uses volatile fields. However, the old specification was so complex that implementors didn’t always follow it, and it allowed confusing and undesirable behavior, such as
immutable objects that weren’t truly immutable.

Deadlocks

Locks and conditions cannot solve all problems that might arise in multithreading. Consider the following situation:

Threads 1 and 2 are clearly blocked. Neither can proceed because the balances in Accounts 1 and 2 are insufficient. Is it possible that all threads are blocked because each is waiting for more money? Such a situation is called a deadlock.

A deadlock situation

A deadlock situation

In our program, a deadlock cannot occur for a simple reason. Each transfer amount is for, at most, $1,000. Because there are 100 accounts and a total of $100,000 in them, at least one of the accounts must have more than $1,000 at any time. The thread moving money out of that account can therefore proceed.

But if you change the run method of the threads to remove the $1,000 transaction limit, deadlocks can occur quickly. Try it out. Set NACCOUNTS to 10. Construct each transfer runnable with a max value of 2 * INITIAL_BALANCE and run the program. The program will run for a while and then hang.

TIP: When the program hangs, type CTRL+. You will get a thread dump that lists all threads. Each thread has a stack trace, telling you where it is currently blocked. Alternatively, run jconsole, as described, and consult the Threads panel.

The Threads panel in jconsole

The Threads panel in jconsole

Another way to create a deadlock is to make the i’th thread responsible for putting money into the i’th account, rather than for taking it out of the i’th account. In this case, there is a chance that all threads will gang up on one account, each trying to remove more money from it than it contains. Try it out. In the SynchBankTest program, turn to the run method of the TransferRunnable class. In the call to transfer, flip fromAccount and toAccount.

Run the program and see how it deadlocks almost immediately. Here is another situation in which a deadlock can occur easily: Change the signalAll method to signal in the SynchBankTest program. You will find that the program hangs eventually.

(Again, it is best to set NACCOUNTS to 10 to observe the effect more quickly.) Unlike signalAll, which notifies all threads that are waiting for added funds, the signal method unblocks only one thread. If that thread can’t proceed, all threads can be blocked. Consider the following sample scenario of a developing deadlock.

All other threads: Transfer $995 from their account to another account Clearly, all threads but Thread 1 are blocked, because there isn’t enough money in their accounts.

Then, Thread 1 calls signal. The signal method picks a thread at random to unblock. Suppose it picks Thread 3. That thread is awakened, finds that there isn’t enough money in its account, and calls await again. But Thread 1 is still running. A new random transaction is generated, say, Thread 1: Transfer $997 to from Account 1 to Account 2

Now, Thread 1 also calls await, and all threads are blocked. The system has deadlocked.

The culprit here is the call to signal. It only unblocks one thread, and it may not pick the thread that is essential to make progress. (In our scenario, Thread 2 must proceed to take money out of Account 2.)

Unfortunately, there is nothing in the Java programming language to avoid or break these deadlocks. You must design your program to ensure that a deadlock situation cannot occur.

Lock Testing and Timeouts

A thread blocks indefinitely when it calls the lock method to acquire a lock that is ownedby another thread. You can be more cautious about acquiring a lock. The tryLock method tries to acquire a lock and returns true if it was successful. Otherwise, it immediately returns false, and the thread can go off and do something else.


You can call tryLock with a timeout parameter, like this:

TimeUnit is an enumeration with values SECONDS, MILLISECONDS, MICROSECONDS, and NANOSECONDS. The lock method cannot be interrupted. If a thread is interrupted while it is waiting to acquire a lock, the interrupted thread continues to be blocked until the lock is available.

If a deadlock occurs, then the lock method can never terminate. However, if you call tryLock with a timeout, then an InterruptedException is thrown if the thread is interrupted while it is waiting. This is clearly a useful feature because it allows a program to break up deadlocks. You can also call the lockInterruptibly method. It has the same meaning as tryLock with an infinite timeout.

When you wait on a condition, you can also supply a timeout:

The await method returns if another thread has activated this thread by calling signalAll or signal, or if the timeout has elapsed, or if the thread was interrupted. The await methods throw an InterruptedException if the waiting thread is interrupted. In the (perhaps unlikely) case that you’d rather continue waiting, use the awaitUninterruptiblymethod instead.

java.util.concurrent.locks.Lock

  • boolean tryLock()
    tries to acquire the lock without blocking; returns true if it was successful. This method grabs the lock if it is available even if it has a fair locking policy and other threads have been waiting.
  • boolean tryLock(long time, TimeUnit unit)
    tries to acquire the lock, blocking no longer than the given time; returns true if it was successful.
  • void lockInterruptibly()
    acquires the lock, blocking indefinitely. If the thread is interrupted, throws an InterruptedException.

java.util.concurrent.locks.Condition

  • boolean await(long time, TimeUnit unit)
    enters the wait set for this condition, blocking until the thread is removed from the wait set or the given time has elapsed. Returns false if the method returned because the time elapsed, true otherwise.
  • void awaitUninterruptibly()
    enters the wait set for this condition, blocking until the thread is removed from the wait set. If the thread is interrupted, this method does not throw an InterruptedException.

Read/Write Locks

The java.util.concurrent.locks package defines two lock classes, the ReentrantLock that we already discussed and the ReentrantReadWriteLock class. The latter is useful when there are many threads that read from a data structure and fewer threads that modify it. In that situation, it makes sense to allow shared access for the readers. Of course, a writer must still have exclusive access.

Here are the steps that are necessary to use read/write locks:

  1. Construct a ReentrantReadWriteLock object:
  2. Extract read and write locks:
  3. Use the read lock in all accessors:
  • Use the write lock in all mutators:

java.util.concurrent.locks.ReentrantReadWriteLock

  • Lock readLock()
    gets a read lock that can be acquired by multiple readers, excluding all writers.
  • Lock writeLock()
    gets a write lock that excludes all other readers and writers.

Why the stop and suspendMethods Are Deprecated

The initial release of Java defined a stop method that simply terminates a thread, and a suspend method that blocks a thread until another thread calls resume. The stop and suspendmethods have something in common: Both attempt to control the behavior of a given thread without the thread’s cooperation. Both of these methods have been deprecated since Java SE 1.2. The stop method is inherently unsafe, and experience has shown that the suspend method frequently leads to deadlocks. In this section, you will see why these methods are problematic and what you can do to avoid problems.

Let us turn to the stop method first. This method terminates all pending methods, including the run method. When a thread is stopped, it immediately gives up the locks on all objects that it has locked. This can leave objects in an inconsistent state.For example, suppose a TransferThread is stopped in the middle of moving money from one account to another, after the withdrawal and before the deposit. Now the bank object is damaged.

Since the lock has been relinquished, the damage is observable from the other threads thathave not been stopped. When a thread wants to stop another thread, it has no way of knowing when the stop method is safe and when it leads to damaged objects. Therefore, the method has beendeprecated. You should interrupt a thread when you want it to stop. The interruptedthread can then stop when it is safe to do so.

NOTE: Some authors claim that the stop method has been deprecated because it can cause objects to be permanently locked by a stopped thread. However, that claim is not valid. A stopped thread exits all synchronized methods it has called—technically, by throwing a ThreadDeath exception. As a consequence, the thread relinquishes the intrinsic object locks that it holds.

Next, let us see what is wrong with the suspend method. Unlike stop, suspend won’t damage objects. However, if you suspend a thread that owns a lock, then the lock is unavailable until the thread is resumed. If the thread that calls the suspend method tries to acquire the same lock, then the program deadlocks: The suspended thread waits to be resumed, and the suspending thread waits for the lock.

This situation occurs frequently in graphical user interfaces. Suppose we have a graphical simulation of our bank. A button labeled Pause suspends the transfer threads, and a button labeled Resume resumes them.

Suppose a paintComponent method paints a chart of each account, calling a getBalances method to get an array of balances. As you will see in the section “Threads and Swing”, both the button actions and the repainting occur in the same thread, the event dispatch thread. Consider the following scenario:

  1. One of the transfer threads acquires the lock of the bank object.
  2. The user clicks the Pause button.
  3. All transfer threads are suspended; one of them still holds the lock on the bank object.
  4. For some reason, the account chart needs to be repainted.
  5. The paintComponent method calls the getBalances method.
  6. That method tries to acquire the lock of the bank object.
    Now the program is frozen.

The event dispatch thread can’t proceed because the lock is owned by one of the suspendedthreads. Thus, the user can’t click the Resume button, and the threads won’t ever resume. If you want to safely suspend a thread, introduce a variable suspendRequested and test it in a safe place of your run method—somewhere your thread doesn’t lock objects that other threads need. When your thread finds that the suspendRequested variable has been set, it should keep waiting until it becomes available again.

The following code framework implements that design:


All rights reserved © 2018 Wisdom IT Services India Pvt. Ltd DMCA.com Protection Status

Core Java Topics