简体   繁体   中英

Thread synchronization with Locks and Conditions


I have a problem to understand Locks and Conditions in Java, i do not understand why my code ends up in a deadlock.
My programm consists of a Mainthread and a Subthread, subthread is a member of Mainthread. Both threads run in an infinite loop, Subthread's loop is supposed to execute exactly one iteration as soon as it receives the signal for startCond from the Mainthread. Mainthread should wait for the finishCond signal to continue.

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockTest {

    public static void main(String[] args) {
        LockTest lt = new LockTest();
        Mainthread m1 = lt.new Mainthread();
        m1.start();
    }

    public class Mainthread extends Thread {
        private Subthread sub = new Subthread();

        public void run(){
            System.out.println("Main start");
            sub.start();

            while(!isInterrupted()) {
                try {
                    sub.getStartLock().lock();
                    sub.getStartCond().signal();
                    sub.getStartLock().unlock();

                    sub.getFinishLock().lock();
                    sub.getFinishCond().await();
                    sub.getFinishLock().unlock();
                    System.out.println("Main done");
                } catch(InterruptedException e) {
                    e.printStackTrace();
                }

            }   
        }
    }

    public class Subthread extends Thread {
        private Lock startLock = new ReentrantLock();
        private Lock finishLock = new ReentrantLock();
        private Condition startCond = startLock.newCondition();
        private Condition finishCond = finishLock.newCondition();


        public Lock getStartLock() {
            return startLock;
        }

        public Lock getFinishLock() {
            return finishLock;
        }

        public Condition getStartCond() {
            return startCond;
        }

        public Condition getFinishCond() {
            return finishCond;
        }

        public void run() {
            System.out.println("Sub start");
            while(!isInterrupted()) {
                try {
                    startLock.lock();
                    startCond.await();
                    startLock.unlock();

                    finishLock.lock();
                    finishCond.signal();
                    finishLock.unlock();

                    System.out.println("Sub done");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}

My expected output would be:
Main done
Sub done
(repeated as many times as it was executed in the loops).

Is there a way to solve this problem easier?

The main thread starts, it creates new sub thread and starts it but calling start on a thread does not mean that the thread would receive the processor imeddiatly and that its code will be actually executed.

Main, callss sub.getStartCond().signal(); but at this moment the sub thread is still not running so it misses this signal.

Main, awaits on the finishCond.

Sub starts executing its run method, it goes to the start condition and waits on it for ever.

The deadlock.

Signal wakes up only CURRENTLY waiting thread, it does not 'remember' previous calls.

Use Semaphore instead http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Semaphore.html

it has the semantic of 'counting the permits'.

There might be a more reliable way to do this. I would recommend using a CountDownLatch initialized with a count of 1, instead of a condition. Both the main and child threads can share the same instance of the latch (since the main owns the child that should be easy). The child will call await() and the main will call countDown() when you need to send the signal to the child. I recommend you make the latch private and final .

class ChildThread extends Thread {
  private final CountDownLatch signal;

  public ChildThread(CountDownLatch signal) {
    this.signal = signal;
  }

  public void run() {
    // The loop is necessary in case we get interrupted.
    while (true) {
      try {
        signal.await();
        break;
      } catch(InterruptedException ignored) {
      }
    }
    // do the work...
  }
}

class MainThread extends Thread {
  private final ChildThread child;
  private final CountDownLatch signalToChild;
  public MainThread() {
    signalToChild = new CountDownLatch(1);
    child = new ChildThread(signalToChild);
  }

  public void run() {
    // I can start the child right away but I'd rather make sure it
    // starts if the main thread has started.
    child.start();
    // prework
    // let's signal the child
    signalToChild.countDown();
    // now the child is working, let's go on with the main thread work
  }
}

This works because main and child thread actually share state, ie, the latch. It does not matter if the main thread decrements the latch before the child thread is actually started, because the child will check this shared state to know if it can start.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM