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

      

+3


source to share


2 answers


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.

+3


source


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 keyword volatile

associated with victim

, 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

in victim

, 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 in while(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 sets victim

    to 0 and starts the loop
  • The second thread calls lock

    and sets victim

    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 to victim

    become 1

So the last call (in a sense) will not complete if both threads call the method lock

multiple times each.

0


source







All Articles