Java thread deadlock example
I'm trying to read "The Art of Multiprocessor Programming" by Herliha and Shavit. In the second chapter, they motivate Peterson's algorithm with two incorrect implementations of locks. I was trying to figure out the problem with the LockTwo class and ended up with the following code that blocks. The sample is displayed after the code where threads do not print all values ββfrom 1 to 100 to deadlock. For a deadlock to occur in any thread, it while(victim ==i){}
must run forever. But if both threads start this loop, one of them should exit, because there victim
cannot be 0 and 1 at the same time. Are you missing something related to the cache?
class Counter
{
private int value;
private int maxValue;
public Counter(int value, int maxValue)
{
this.value = value;
this.maxValue = maxValue;
}
public int getValue()
{
return value;
}
public int getMaxValue()
{
return maxValue;
}
public void incrementValue()
{
value++;
}
}
class ThreadID
{
private static volatile int nextID = 0;
private static ThreadLocalID threadID = new ThreadLocalID();
public static int get()
{
return threadID.get();
}
public static void reset()
{
nextID = 0;
}
private static class ThreadLocalID extends ThreadLocal<Integer>
{
protected synchronized Integer initialValue()
{
return nextID++;
}
}
}
class IncrementThread implements Runnable
{
private static Counter c = new Counter(0,100);
private static LockTwo lock2 = new LockTwo();
public IncrementThread()
{
}
public void run()
{
int j = ThreadID.get();
String m = "Thread ID " + j;
//System.out.println(m);
while(c.getValue() < c.getMaxValue())
{
lock2.lock();
c.incrementValue();
String s = "Value in counter is " + c.getValue() + " in thread " + j;
System.out.println(s);
lock2.unlock();
}
}
}
interface Lock
{
public void lock();
public void unlock();
}
class LockTwo implements Lock
{
private int victim;
public LockTwo() {};
public void lock()
{
int i = ThreadID.get();
victim = i;
System.out.println("Trying to acquire lock in thread " + i +" with victim " + victim);
while(victim == i) {}
System.out.println("Lock acquired in thread " + i + " with victim " + victim);
}
public void unlock()
{
int i = ThreadID.get();
//victim = i;
System.out.println("Lock released in thread " + i + " with victim " + victim);
}
}
public class SharedCounter
{
public static void main(String[] args) throws InterruptedException
{
Thread thread[] = new Thread[2];
for (int i = 0; i < thread.length; i++)
{
thread[i] = new Thread(new IncrementThread());
thread[i].start();
}
}
}
Output example
$java SharedCounter
Trying to acquire lock in thread 0 with victim 1
Trying to acquire lock in thread 1 with victim 1
Lock acquired in thread 0 with victim 1
Value in counter is 1 in thread 0
Lock released in thread 0 with victim 1
Trying to acquire lock in thread 0 with victim 0
Lock acquired in thread 1 with victim 0
Value in counter is 2 in thread 1
Lock released in thread 1 with victim 0
Trying to acquire lock in thread 1 with victim 1
Lock acquired in thread 0 with victim 1
Value in counter is 3 in thread 0
Lock released in thread 0 with victim 1
Trying to acquire lock in thread 0 with victim 0
source to share
I suspect the problem is that victim
it is not volatile.
Variables in java can be cached locally to a stream.
This means that each thread has its own kind of victim, each with its own id.
That is, Thread 0 has victim == 0
, and Thread1 hasvictim == 1
Using volatile tells jvm that the variable will be used across threads and that it cannot be cached.
source to share
This answer builds on Jim's answer and comments related to it.
If the problem is with the missing keyword
volatile
, the threads must immediately deadlock, they can never increment the counter. - sarva
This is not true, there is no way to guarantee that the written meaning will propagate to other threads.
BTW, the implementation
LockTwo
in ArtofMP has a keywordvolatile
associated withvictim
, but errata asks to remove that keyword. - sarva
This is only because consistency with other algorithms is presented in the same chapter. Pragma 2.3.1 says that victim
, label
etc. In practice, should be declared volatile
, but then it is victim
declared volatile
in Figure 2.5 anyway.
If I add the keyword
volatile
invictim
, then at the very end there will be a dead end, when one thread completes execution, increasing the counter to 100, and the other thread waits inwhile(victim == i) {}
without changing the value of the victim. - sarva
This is the behavior you want. Consider a single threaded program, the following lines from your program don't make sense:
victim = i; while (victim == i) {}
Without an additional thread, it would be an endless loop.
We can get a substantially equivalent situation in a two-threaded program, when only one of the threads tries to acquire the lock (i.e. calls a method lock
).
If both threads call the method lock
, we get the behavior you observed when a deadlock occurs at the end:
- The first thread calls
lock
and setsvictim
to 0 and starts the loop - The second thread calls
lock
and setsvictim
to 1 and starts the loop - The first thread sees that
victim
it is now 1 and enters the critical section - If the first thread never calls the method again
lock
, the second thread will hang in a loop forever, waiting tovictim
become 1
So the last call (in a sense) will not complete if both threads call the method lock
multiple times each.
source to share