简体   繁体   中英

NotifyAll method does not wake up a thread

There are 3 simple threads ( two should wait, one - awake threads one by one )

public static void main(String[] args) {

       Main main = new Main(); // instance to call methods and synchronize.

Thread thread1 = new Thread(() -> main.methodA());
Thread thread2 = new Thread(() -> main.methodA());
Thread thread3Notify = new Thread(() -> main.methodB());

    thread1.start();
    thread2.start();
    thread3Notify.start();
}

Here is a method the only purpose of which is to make threads wait

  public synchronized void methodA()   {
        System.out.println("I enter methodA" + Thread.currentThread());
                this.wait();
        System.out.println("I am awake " + Thread.currentThread());
}

Method to wake up 2 threads one by one

    public synchronized void methodB() {
    this.notifyAll();
    sleep(1000);
    this.notifyAll();
}

Here is a problem, this is what I see in a console:

I enter methodAThread[Thread-0,5,main]
I enter methodAThread[Thread-1,5,main]
I am awake Thread[Thread-0,5,main]

MethodB wakes up the first thread but second waits forever, no matter how many times I call notifyAll()

It's a timing issue. If I modify your code:

   public synchronized void methodB() {
      try {
         this.notifyAll();
         Thread.sleep( 1000 );
         this.notifyAll();
         System.out.println( "Notified..." );
      } catch( InterruptedException ex ) {
         Logger.getLogger( NotifyAllTesting.class.getName() ).log( Level.SEVERE, null, ex );
         System.out.println( ex );
      }
   }

Then what happens is this on the output:

I enter methodAThread[Thread-0,5,main]
Notified...
I enter methodAThread[Thread-1,5,main]
I am awake Thread[Thread-0,5,main]

So what happens is:

  1. First thread enters methodA
  2. Second thread attempts to enter methodA and blocks.
  3. First thread waits()
  4. Third thread executes both notifyAll()
  5. First thread wakes up from wait() and continues
  6. Second thread wakes up and enters methodA and blocks on wait()

The order of 6. and 7. could be reversed, but the result is the same.

It's important to realize that you have two places in methodA where a thread can block, and if you miss even one of the notifyAll then that thread is going to block. Since there is a println statement in methodA , it takes a long time to execute. That means either you are missing the first notifyAll due to timing, or the thread is blocked trying to enter methodA , but it is still blocked when the first notifyAll occurs, because the other thread is still executing the println , so the notifyAll has no real effect.

@markspace is correct.

It is a timing issue, the issue its not related to the second notifyAll but is related to the first one.

I quick test your code. in fact the first notify get sended to soon while the waiting thread are not in the waiting state (During thread generation or during the println call that take time to execute).

To fix that you could make use a variables and check if the thread you strated before are ready to be used (a semphore is a good solution).

     public synchronized void methodB() throws InterruptedException {
            TimeUnit.MILLISECONDS.sleep(1000); // Wait a few before notifyAll.  
            notifyAll();
            TimeUnit.MILLISECONDS.sleep(1000);
            System.out.println("Second!");
            notifyAll();
        }

The comment by markspace suggesting the use of a semaphore is a good one. Following is an example using the Ada programming language which implements a semaphore using an Ada protected object.

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   protected lock is
      entry acquire;
      entry release;
   private
      Locked : Boolean := True;
   end lock;

   protected body lock is
      entry acquire when not Locked is
      begin
         Locked := True;
      end acquire;

      entry release when Locked is
      begin
         Locked := False;
      end release;
   end lock;

   task type Task_A (Id : Positive);

   task body Task_A is
   begin
      Put_Line ("I entered task_A : " & Id'Image);
      lock.acquire;
      Put_Line ("I am awake       : " & Id'Image);
   end Task_A;

   task B;
   task body B is
      Id : String := "Task B";
   begin
      delay 0.05;
      Put_Line (Id & " awake.");
      lock.release;
      delay 1.0;
      lock.release;
   end B;

   A1 : Task_A (1);
   A2 : Task_A (2);
begin
   null;
end Main;

The protected object Lock handles the concurrency issues. Ada protected entries have a boundary condition. A task calling an entry with a closed boundary condition (the condition evaluates to False) are suspended in an queue for that entry until the boundary condition becomes false. The boundary condition for the acquire entry states that the Lock protected object must not be locked. The Locked private Boolean variable is initialized to True, causing all tasks calling the acquire entry to be suspended until Locked is true. The Release entry sets Locked to False, allowing one task to execute the acquire entry in FIFO order. During the execution of Acquire entry the Locked private variable is set to True, causing any other tasks in the acquire entry queue to remain suspended.

Task B simply states when it is awake, calls lock.release, delays for 1.0 seconds and calls lock.release again.

The output of this program is:

I entered task_A :  1
I entered task_A :  2
Task B awake.
I am awake       :  1
I am awake       :  2

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