2

I'm currently studying about signaling in threads and came across this article for signaling via shared objects,
http://tutorials.jenkov.com/java-concurrency/thread-signaling.html

It says that we can create a shared object and pass that object to threads which, threads can use to signal each other.

Following is the snippet provided for shared object,

public class MySignal{
  protected boolean hasDataToProcess = false;

  public synchronized boolean hasDataToProcess(){
    return this.hasDataToProcess;
  }

  public synchronized void setHasDataToProcess(boolean hasData){
    this.hasDataToProcess = hasData;  
  }
}

I tried to use it in my class as,

class MySignal {
    boolean hasDataToProcess = false;

    public MySignal(boolean defaultValue) {
        this.hasDataToProcess = defaultValue;
    }

    public synchronized boolean hasDataToProcess() {
        return this.hasDataToProcess;
    }

    public synchronized void setHasDataToProcess(boolean hasDataToProcess) {
        this.hasDataToProcess = hasDataToProcess;
    }
}

class MyThreadRunnable implements Runnable {
    MySignal sharedSignal;

    MyThreadRunnable(MySignal signal) {
        this.sharedSignal = signal;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " starts running..");

        synchronized (sharedSignal) {
            System.out.println(Thread.currentThread().getName() + " accessing sharedSignal");

            while(sharedSignal.hasDataToProcess()) {
                sharedSignal.setHasDataToProcess(false);
                try {
                      System.out.println(Thread.currentThread().getName() + " going to sleep");
                      Thread.sleep(3000);
                } catch (InterruptedException e) {
                }
            }

            sharedSignal.setHasDataToProcess(true);
            System.out.println(Thread.currentThread().getName() + " ended.");
        }
    }

}

public class Test2 {
    public static void main(String[] args) {
        MySignal mySignal = new MySignal(true);

        MyThreadRunnable t1 = new MyThreadRunnable(mySignal);
        MyThreadRunnable t2 = new MyThreadRunnable(mySignal);

        Thread t3 = new Thread(t1);
        Thread t4 = new Thread(t2);

        t3.start();
        t4.start();
    }
}

This provided the expected output as,

Thread-1 starts running..
Thread-0 starts running..
Thread-1 accessing sharedSignal
Thread-1 going to sleep
Thread-1 ended.
Thread-0 accessing sharedSignal
Thread-0 going to sleep
Thread-0 ended.

But even if I remove the synchronized on the MySignal methods, this provides the same output as sharedSignal object is locked by one of the threads. And, if I remove only the synchronized in run(), it does not work properly as one of the threads end before even going to sleep.

So this code is only running correctly due to the lock on sharedSignal object.

Is this how the signaling has to be used?
My intuition says that I've missed something. I tried searching for a good example but no luck so far. Any help would be appreciated.

dSanders
  • 165
  • 11
  • 1
    Any variable shared across threads must be `volatile`, eg `protected volatile boolean hasDataToProcess = false;`, because otherwise threads may (and usually do) cache the value, effectively ignoring changes to it made in other threads. – Bohemian Sep 15 '19 at 07:00
  • @Bohemain I have this doubt as well. Since objects are stored on the heap, I don't need to declare them volatile, do I? For primitives, caching makes sense. Is it the same for objects as well? – Prashant Pandey Sep 15 '19 at 07:10
  • 1
    @Bohemian I have the same doubt as Prashant. Do we need to put *volatile* for object members too for signaling to work properly? – dSanders Sep 15 '19 at 07:21
  • 2
    As a note, regardless of the semantics right here, the class `AtomicBoolean` was added for exactly this case (though a `ConcurrentQueue` is usually a better design choice). Concurrency is hard; use the tools that have been carefully tested to make it easier. – chrylis -cautiouslyoptimistic- Sep 15 '19 at 07:24
  • Yes, for objects too. The *reference* to objects, and the *value* of primitives, must be volatile to guarantee visibility of changes in threads other then the one that made the change. – Bohemian Sep 15 '19 at 07:25
  • 2
    After reading that article, it's clear that it was obsolete 8 years before it was written with the release of Java 6. – chrylis -cautiouslyoptimistic- Sep 15 '19 at 07:26
  • @Bohemian The question as phrased is a bit unclear. Marking a variable `volatile` will not make _changes to the members of the object referenced_ visible, only the immediately marked variable. – chrylis -cautiouslyoptimistic- Sep 15 '19 at 07:27
  • @Bohemian Wouldn't that create a performance issue if the object size is large and frequent read/write is happening on the object's members? – dSanders Sep 15 '19 at 07:28
  • with `synchronized` statement, the signal does nothing, since whole block is already synchronized; without it, the problem is logic: first thread sees `true` enter loop and sets to `false`, 2nd thread sees `false`, does not enter loop and terminates (acting as implemented, expected - there is no data to process, since 1st thread is already processing it) Note: checking flag and setting flag should be done in an atomic way (synchronized) – user85421 Sep 15 '19 at 07:49
  • @Bohemian You're incorrect - `volatile` is not required, correctly published data can be achieved with only `synchronized` (there are a few other ways too but they are less generally usable - such as starting/joining threads, etc.) - please read up on the Java Memory Model (section 17.4 of the JLS - https://docs.oracle.com/javase/specs/jls/se12/html/jls-17.html#jls-17.4) – Erwin Bolwidt Sep 15 '19 at 08:15

1 Answers1

0

But even if I remove the synchronized on the MySignal methods, this provides the same output as sharedSignal object is locked by one of the threads

Removing the synchronized from the methods won't make a difference as there is already a synchronized block guarding the method access from different threads.

And, if I remove only the synchronized in run(), it does not work properly as one of the threads end before even going to sleep.

But if you remove the the synchronized block then the contents of the block are not executed in an atomic way.

What I mean is without the synchronized block the any thread can call the sharedSignal.hasDataToProcess() get the lock on the MySignal object and then release it after it is done with the method then another thread is free to call the sharedSignal.setHasDataToProcess(false); as the lock on the MySignal instance was already released by the earlier thread when it was done with the method.

//Acquires lock for the entire block
synchronized (sharedSignal) { 
     System.out.println(Thread.currentThread().getName() + " accessing sharedSignal");
     while(sharedSignal.hasDataToProcess()) {
           sharedSignal.setHasDataToProcess(false);
             try {
                System.out.println(Thread.currentThread().getName() + " going to sleep");
                Thread.sleep(3000);
             } catch (InterruptedException e) {
             }
      }        
      sharedSignal.setHasDataToProcess(true);
      System.out.println(Thread.currentThread().getName() + " ended.");
}

Now without the synchronized block, the code of the block is not executed in an atomic way:

System.out.println(Thread.currentThread().getName() + " accessing sharedSignal");
//say thread1 acquires lock here
while(sharedSignal.hasDataToProcess()) {
     //thread1 releases lock here, thread2 can acquire lock on the same object
     sharedSignal.setHasDataToProcess(false);
        try {
            System.out.println(Thread.currentThread().getName() + " going to sleep");
            Thread.sleep(3000);
         } catch (InterruptedException e) {
         }
   }        
   sharedSignal.setHasDataToProcess(true);
   System.out.println(Thread.currentThread().getName() + " ended.");
}
Fullstack Guy
  • 16,368
  • 3
  • 29
  • 44
  • Okay, so the *synchronized* on method is only working from, when the thread accesses it and till the method execution is finished. – dSanders Sep 15 '19 at 07:19
  • @dSanders yes, the thread holds the lock on the `this` instance for a `synchronized` instance method, so when it starts executing it acquires it and when its done it will release the lock. – Fullstack Guy Sep 15 '19 at 07:21
  • So in order to use the signaling using shared objects, we **always** need to put a lock on the shared object and, without it there is no guarantee that signaling will work properly? – dSanders Sep 15 '19 at 07:24
  • @dSanders lock should be acquired for exclusive access to a block of code and also for using the wait/notify mechanism used in thread signalling. Plus as Bohemian said mark your flag as volatile otherwise some updates might not be seen by threads. – Fullstack Guy Sep 15 '19 at 07:30
  • Okay, so in order to use shared objects, we need to make its members *volatile* and, put locks on only those blocks of code which should be executed strictly by one thread at a time. – dSanders Sep 15 '19 at 07:44
  • And in the presented example, it should be on the *sharedSignal* object. – dSanders Sep 15 '19 at 07:45
  • Thanks, I got it. One more thing, wouldn't putting volatile on objects members create a performance issue if frequent read/write is happening on that shared object ? – dSanders Sep 15 '19 at 07:50
  • @dSanders you can find the explanation of the volatile cost in this [answer](https://stackoverflow.com/questions/4633866/is-volatile-expensive) – Fullstack Guy Sep 15 '19 at 08:01
  • I think there is no need for `volatile`, JLS suggest it is an alternative to locking (my emphasis); "As a rule, to ensure that shared variables are consistently and reliably updated, a thread should ensure that it has exclusive use of such variables by obtaining a lock that, conventionally, enforces mutual exclusion for those shared variables. The Java programming language provides a **second mechanism**, volatile fields, that is more convenient than locking for some purposes." – user85421 Sep 15 '19 at 08:02