简体   繁体   中英

Java Multithreading Execution Blocked

for learning purpose i have tried to implements a queue data-structure + Consumer/producer chain that is thread-safe, for learning purpose too i have not used notify/wait mechanism :

SyncQueue :

package syncpc;

/**
 * Created by Administrator on 01/07/2009.
 */
public class SyncQueue {

   private int val = 0;
   private boolean set = false;


   boolean isSet() {
      return set;
   }

   synchronized  public void enqueue(int val) {
      this.val = val;
      set = true;
   }

   synchronized public int dequeue()  {
      set = false;
      return val;
   }
}

Consumer :

package syncpc;

/**
 * Created by Administrator on 01/07/2009.
 */
public class Consumer implements Runnable {
    SyncQueue queue;

    public Consumer(SyncQueue queue, String name) {
        this.queue = queue;

        new Thread(this, name).start();
    }


    public void run() {

        while(true) {
            if(queue.isSet()) {
                System.out.println(queue.dequeue());
            }

        }
    }
}

Producer :

package syncpc;

import java.util.Random;

/**
 * Created by Administrator on 01/07/2009.
 */
public class Producer implements Runnable {
    SyncQueue queue;

    public Producer(SyncQueue queue, String name) {

        this.queue = queue;
        new Thread(this, name).start();
    }

    public void run() {
        Random r = new Random();

        while(true) {
            if(!queue.isSet()) {
                    queue.enqueue(r.nextInt() % 100);
            }
        }
    }
}

Main :

import syncpcwn.*;

/**
 * Created by Administrator on 27/07/2015.
 */
public class Program {

    public static void main(String[] args) {
        SyncQueue queue  = new SyncQueue();

        new Producer(queue, "PROCUDER");
        new Consumer(queue, "CONSUMER");
    }


}

The problem here, is that if isSet method is not synchronized , i got an ouput like that :

97,
55

and the program just continue running without outputting any value. while if isSet method is synchronized the program work correctly.

i don't understand why, there is no deadlock, isSet method just query the set instance variable without setting it, so there is no race condition.

set needs to be volatile :

private boolean volatile set = false;

This ensures that all readers see the updated value when a write completes. Otherwise they will end up seeing the cached value. This is discussed in more detail in this article on concurrency, and also provides examples of different patterns that use volatile .

Now the reason that your code works with synchronized is probably best explained with an example. synchronized methods can be written as follows (ie, they are equivalent to the following representation):

public class SyncQueue {

   private int val = 0;
   private boolean set = false;


   boolean isSet() {
      synchronized(this) {
          return set;
      }
   }

   public void enqueue(int val) {
      synchronized(this) {
          this.val = val;
          set = true;
      }
   }

   public int dequeue()  {
      synchronized(this) {
          set = false;
          return val;
      }
   }
}

Here, the instance is itself used as a lock. This means that only thread can hold that lock. What this means is that any thread will always get the updated value because only one thread could be writing the value, and a thread that wants to read set won't be able to execute isSet until the other thread releases the lock on this , at which point the value of set will have been updated.

If you want to understand concurrency in Java properly you should really read Java: Concurrency In Practice (I think there's a free PDF floating around somewhere as well). I'm still going through this book because there are still many things that I do not understand or am wrong about.


As matt forsythe commented, you will run into issues when you have multiple consumers. This is because they could both check isSet() and find that there is a value to dequeue, which means that they will both attempt to dequeue that same value. It comes down to the fact that what you really want is for the "check and dequeue if set" operation to be effectively atomic, but it is not so the way you have coded it. This is because the same thread that initially called isSet may not necessarily be the same thread that then calls dequeue . So the operation as a whole is not atomic which means that you would have to synchronize the entire operation.

The problem you have is visibility (or rather, the lack of it).

Without any instructions to the contrary, the JVM will assume that the value assigned to a variable in one thread need not be visible to the other threads. It may be made visible sometimes later (when it's convenient to do so), or maybe not ever. The rules governing what should be made visible and when are defined by the Java Memory Model and they're summed up here . (They may be a bit dry and scary at first, but it's absolutely crucial to understand them.)

So even though the producer sets set to true , the consumer will continue to see it as false. How can you publish a new value?

  1. Mark the field as volatile . This works well for primitive values like boolean , with references you have to be a bit more careful.
  2. synchronized provides not just mutual exclusion but also guarantees that any values set in it will be visible to anyone entering a synchronized block that uses the same object. (This is why everything works if you declare the isSet() method synchronized .)
  3. Using a thread-safe library class, like the Atomic* classes of java.util.concurrent

In your case volatile is probably the best solution because you're only updating a boolean , so atomicity of the update is guaranteed by default.


As @matt forsythe pointed out, there is also a TOCTTOU issue with your code too because your threads can be interrupted by another between isSet() and enqueue()/dequeue() .

I assume that when we get stuck in threading issue, the first step was to make sure that both the threads are running well. ( i know they will as there are no locks to create deadlock)

For that you could have added a printf statement in enqueue function as well. That would make sure that enqueue and dequeue threads are running well.

Then second step should have been that "set" is the shared resource, so is the value toggling well enough so that code can run in desired fashion.

I think if you could reason and put the logging well enough, you can realize the issues in problem.

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