Deprecated Thread Methods
There are a few operations defined by the Thread class that are not recommended for use and are now marked as @Deprecated. These include the following methods of the Thread class:
final void resume()
final void stop()
final void suspend()
Their initial design intention was to control the thread lifecycle. However, Java concurrency design has changed since these operations were introduced. The new concurrency API allows concurrent programming at a higher level than controlling the threads. The legacy approach to controlling threads was to force a thread to become suspended, to resume, or to stop. Deprecated concurrency methods are likely to result in memory corruption. The code in this chapter does not use any of the deprecated methods.
22.5 Thread Issues
In this section, we briefly outline some problems that occur often in multithreaded applications. Although some solutions are mentioned, there is no silver bullet that will solve all such problems. In the worst-case scenario, the application may need to be redesigned all over again.
Liveness and Fairness
Multithreaded applications strive for liveness and fairness.
The fairness property of a multithreaded application refers to threads in the application getting a fair chance to run—so that all threads in the application can make progress in their work, and no thread monopolizes the CPU at the expense of others.
The liveness property of a multithreaded application refers to the ability of the threads to execute in a timely manner—meaning performing as expected and making continuous progress in their work.
The rest of this section describes three liveness issues common in multithreaded applications: deadlock, livelock, and starvation.
Deadlock
A deadlock is a situation where threads are holding locks on objects that other threads need—a thread is waiting for an object lock that another thread holds, and this second thread is waiting for an object lock that the first thread holds. Since each thread is waiting for the other thread to release a lock, they both remain waiting forever in a waiting state and never make any progress. The threads are said to be deadlocked.
A deadlock is depicted in Figure 22.14. Thread t1 has a lock on object o1, but cannot acquire the lock on object o2. Thread t2 has a lock on object o2, but cannot acquire the lock on object o1. They can only proceed if one of them releases a lock the other one wants, which is never going to happen.
Figure 22.14 Deadlock
The situation in Figure 22.14 is implemented in Example 22.9. Thread t1 at (3) tries to synchronize at (4) and (5), first on string o1 at (1), then on string o2 at (2), respectively. The thread t2 at (6) does the opposite. It synchronizes at (7) and (8), first on string o2 and then on string o1, respectively. A deadlock can occur as explained previously, and the print statement at (11) will never be executed.
However, the potential of a deadlock in the situation in Example 22.9 is easy to fix. If the two threads acquire the locks on the objects in the same order, then mutual lock dependency is avoided and a deadlock can never occur. This means having the same locking order at (4) and (5) as at (7) and (8). In general, the cause of a deadlock is not always easy to discover, let alone easy to fix.
Example 22.9 Deadlock
public class DeadLockDanger {
public static void main(String[] args) {
String o1 = “o1 ” ; // (1)
String o2 = “o2 “; // (2)
Thread t1 = (new Thread(“t1”) { // (3)
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (true) {
synchronized(o1) { // (4)
System.out.println(threadName + ” acquired ” + o1);
synchronized(o2) { // (5)
System.out.println(threadName + “: ” + o1 + o2);
}}}}});
Thread t2 = (new Thread(“t2”) { // (6)
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (true) {
synchronized(o2) { // (7)
System.out.println(threadName + ” acquired ” + o2);
synchronized(o1) { // (8)
System.out.println(threadName + “: ” + o2 + o1);
}}}}});
t1.start(); // (9)
t2.start(); // (10)
System.out.println(“Exiting main.”); // (11)
}
}
It is possible that the program in Example 22.9 might run without a deadlock, depending on how the locks on the two String objects are acquired by each thread. However, after the following output from the program, a deadlock occurs and the program never terminates:
t1 acquired o1
t2 acquired o2