0

Main thread creates child thread. Parent needs some work from child, but not all of it, so parent must wait until child finish that work (child will keep doing some other work).

I would like to achieve it with monitors so I coded the following:

public class WaitChildThreadMonitor {

public static final int TOTAL_COUNT_AMOUNT = 1_000;
static int count = 0;

class Child implements Runnable {

    @Override
    public void run() {
        work();
    }

    public synchronized void work() {

        letParentWaitForThis();

        for (int i = 0; i < TOTAL_COUNT_AMOUNT; i++)
            ++WaitChildThreadMonitor.count;

        this.notifyAll();

        // More child work that parent doesn't need right now
        //  ...
        for (int i = 0; i < TOTAL_COUNT_AMOUNT; i++)
            ++WaitChildThreadMonitor.count;
    }

    private void letParentWaitForThis() {

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {}

    }

    public synchronized void waitForWork() throws InterruptedException {
        this.wait();
    }
}

void main() throws InterruptedException {

    Child child = new Child();
    Thread childThread = new Thread(child);

    // If the next two methods doesn't execute atomically,
    //      parent execution gets blocked forever
    childThread.start();
    child.waitForWork();

    System.out.printf("Count value is %d\n", WaitChildThreadMonitor.count);
    childThread.join();

}

public static void main(String[] args) throws InterruptedException {
    (new WaitChildThreadMonitor()).main();
}

}

The problem is that if child executes "this.notifyAll()" after he finished his main work before parent executes "this.wait()" in "child.waitForWork()", parent won't get notified and will get blocked forever.

I tried to solve it forcing a context switch before child start his work using Thread.sleep() method. It doesn't seem to work as expected.

With sleep and without sleep, sometimes parent gets blocked and program never ends, sometimes it ends properly (I guess because parent waited before child notified).

How can I fix this?

Thanks in advance!

JaviOverflow
  • 1,434
  • 2
  • 14
  • 31
  • 2
    This is a basic producer-consumer problem. The children produce results and the parent consumes them. Use the standard approach(es) to this problem and it will work. – markspace Jan 04 '16 at 23:00
  • _...forcing a context switch..._ There is no such thing as "context switch" in the Java language, and therefore no way in Java that you can force one to happen. Let's say you have three runnable threads, on a system with four CPUs. One of the threads calls `sleep()`. How's that going to affect any other thread? It won't. Every other thread that is able to run already will be running. – Solomon Slow Jan 05 '16 at 14:15

2 Answers2

2

You must not call wait if the thing you want to wait for has already happened. That's the reason the method that calls wait is synchronized -- so you can check the shared state that represents the thing you're waiting for.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
0

So this is a standard producer-consumer problem. A long time ago, I wrote an implementation using only synchronized and wait-notify. I don't see what your code produces; this code just uses int as the thing produced. Change the type of the array inside Storage for some other class type.

package quicktest;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 *
 * @author Brenden Towey
 */
public class ProducerConsumer {
   public static void main(String[] args) throws InterruptedException {
      Storage circularBuffer = new Storage();
      Counter producer1 = new Counter( circularBuffer, 1000 );
      Counter producer2 = new Counter( circularBuffer, 2000 );
      Counter producer3 = new Counter( circularBuffer, 3000 );
      Counter producer4 = new Counter( circularBuffer, 4000 );

      ExecutorService exe = Executors.newCachedThreadPool();

      exe.execute( producer1 );
      exe.execute( producer2 );
      exe.execute( producer3 );
      exe.execute( producer4 );

      Printer consumer = new Printer( circularBuffer );
      exe.execute( consumer );

      Thread.sleep( 100 );// wait a bit
      exe.shutdownNow();
      exe.awaitTermination( 10, TimeUnit.SECONDS );
   }
}

// Producer
class Counter implements Runnable {
   private final Storage output;
   private final int startingValue;

   public Counter(Storage output, int startingValue) {
      this.output = output;
      this.startingValue = startingValue;
   }

   @Override
   public void run() {
         try {
            for( int i = startingValue; ; i++ ) 
               output.put(i);
         } catch (InterruptedException ex) {
            // exit...
         }
   }

}

class Storage {
   private final int[] buffer = new int[20];
   private int head;
   private int count;
   public synchronized void put( int i ) throws InterruptedException {
      while( count == buffer.length ) wait();// full
      buffer[head++] = i;
      head %= buffer.length;
      count++;
      notifyAll();
   }
   public synchronized int get() throws InterruptedException {
      while( count == 0 ) wait(); // empty
      int tail = (head - count) % buffer.length;
      tail = (tail < 0) ? tail + buffer.length : tail;
      int retval = buffer[tail];
      count--;
      notifyAll();
      return retval;
   }
}

// Consumer
class Printer implements Runnable {
   private final Storage input;

   public Printer(Storage input) {
      this.input = input;
   }

   @Override
   public void run() {
         try {
            for( ;; ) 
               System.out.println( input.get() );
         } catch (InterruptedException ex) {
            // exit...
         }
   }
}
markspace
  • 10,621
  • 3
  • 25
  • 39