1

I have two Java threads that need to talk to each other and I'm not sure I'm going about this the right way.

The basic is this: I have on thread, the "runner" which will start off a long-running process by making a call to the command line using the the Apache exec library. Another thread, the "monitor", will periodically monitor and report on the state of the system.

Part of this involves returning the lines that the runner process got from stdout. Currently, the runner and monitor run as separate threads. The monitor has a reference to the runner and can access the output collected (so far) by calling the runner's "get stdout" method.

This seems to be working fine, but the whole setup just doesn't seem very good to me. Partly, I'm worried this implementation isn't thread-safe, though I'm not even sure how to test that. It's been quite a while since I've done multithreaded programming, so I'm wondering if there's a better/cleaner way to do what I'm trying to do. The code looks something like this:

public class Runner implements Callable<String> 
{
  private ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  private ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
  public String getStdOutLines()
  {
    return new String(outputStream.toByteArray());
  }
  public String getStdErrLines()
  {
    return new String(errorStream.toByteArray());
  }

  @Override
  public String call() throws Exception
  {
    /*
    Use apache exec to begin long-running process.
    stdout and stderr are written to two local members (outputStream and errorStream).
    This is done by the apache exec library.
    */
    PumpStreamHandler streamHandler = new PumpStreamHandler(this.outputStream, this.errorStream);
    DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
    executor.setStreamHandler(streamHandler);
    CommandLine cli = //Details not important here.
    executor.execute(cli, resultHandler);
    resultHandler.waitFor();
    String resultsString = generateFinalResultString(outputStream, errorStream); //implementation details of this not important here.
    return resultsString; /*some string calculated after the process has completed.*/
  }
}

public class Monitor implements Runnable
{
  private Runner runner;

  public void setRunner(Runner r)
  {
    this.runner = r;
  }

  @Override
  public void run()
  {
    try
    {
      while (!Thread.currentThread().interrupted())
      {
        //Call the functions on the other thread to get the stdout and stderr
        commandStdOut = this.runner.getStdOutLines();
        commandStdErr = this.runner.getStdErrLines();
        doSendMonitoringMessage(commandStdOut, commandStdErr);
        Thread.sleep(SOME_DELAY);
      }
    }
    catch(InterruptedException e)
    {
       System.out.println("Monitor is shutting down");
    }
  }
}

public class ExecuteAllThis
{
   public void doTheStuff()
   {
      System.out.println("begining...");
      Runner runner = new Runner();
      Monitor monitor = new Monitor();

      monitor.setRunner(runner);
      ExecutorService xservice = Executors.newFixedThreadpool(2);
      xservice.execute(monitor);
      Future<String> runnerFuture = xservice.submit(runner);
      String result = runnerFuture.get(0);
      System.out.println("result is: "+result);
   }

EDIT: I've added some detail about how I'm calling the long-running process which writes to the stdout and stderr strings, since that is more relevant than I initially realized.

FrustratedWithFormsDesigner
  • 26,726
  • 31
  • 139
  • 202
  • Do you just have two threads ? Most importantly, are the two threads using the same functions of runner? –  May 11 '15 at 19:10
  • @DAO: Yes, there should always be exactly 1 monitor and exactly 1 runner. – FrustratedWithFormsDesigner May 11 '15 at 19:12
  • So it's ok. you do not have access concurrence. –  May 11 '15 at 19:15
  • If for some reasons you have multiple threads accessing the same functions of runner, you'd have to add the "synchronized" keyword to those functions. –  May 11 '15 at 19:17
  • @DAO There is concurrent access issues with the `getStdOutLines` and `getStdErrLines`. The Runner can be building those strings while the Monitor is attempting to read them. – MadConan May 11 '15 at 19:19
  • 1
    Ok. I see you need to use either synchronized statements or a mutex. See http://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html http://goose.ycp.edu/~dhovemey/spring2011/cs365/lecture/lecture17.html and http://stackoverflow.com/a/12510490/4624062. I strongly recommend mutexes –  May 11 '15 at 19:28
  • 1
    On the runner side, lock the mutex, build the string then unlock it. –  May 11 '15 at 19:30
  • @DAO: The strings are built by apache commons exec library. I create an OutputStream and pass it to the apache Executor - I'm not sure I can have much control over how it's built. Maybe if I created my own implementation of OutputStream...? – FrustratedWithFormsDesigner May 11 '15 at 19:49
  • Actually, you pass an `InputStream` to receive the output from the running process, see [here](https://commons.apache.org/proper/commons-exec/apidocs/org/apache/commons/exec/DefaultExecutor.html#setStreamHandler%28org.apache.commons.exec.ExecuteStreamHandler%29) and [here](https://commons.apache.org/proper/commons-exec/apidocs/org/apache/commons/exec/ExecuteStreamHandler.html). – vanOekel May 11 '15 at 20:23
  • @vanOekel: I am currently using `PumpStreamHandler` (https://commons.apache.org/proper/commons-exec/apidocs/org/apache/commons/exec/PumpStreamHandler.html) which takes two `OutputStream` objects as arguments to the constructor. – FrustratedWithFormsDesigner May 11 '15 at 20:26
  • 1
    @FrustratedWithFormsDesigner you do not have to see what is happening inside your apache function, you can lock before calling your appache function then unlock after –  May 11 '15 at 21:11

1 Answers1

0

Your implementation is fine.

However, in theory, variables outputStream and errorStream should be volatile, otherwise it is possible that the monitor thread will not get the latest updates - in theory.

Another reason for using volatile for shared variables, in the general case, is to prevent reading corrupt states. That reason does not apply here though since String is immutable.

Another advice, if you are not sure, use the good old synchronized. It might be a little slower than volatile, but that is not usually a problem; and certainly not in your case.

ZhongYu
  • 19,446
  • 5
  • 33
  • 61