Search code examples
javamultithreadingperformanceparallel-processingproducer-consumer

Why is this multithreaded program getting stuck at infinite loop?


The below program is a simple threaded program . For some reason which i am not able to figure , its getting stuck at infinite loop of both produce() and consume() methods simultaneously in both the threads.

It produces the output for a few times and then there is no output at the console. So I presume its getting stuck at the loop.

My question is , since the loop depends on the value of the flag valueSet of the same object of Item class , valueSet can't be both true and false at the same time . So either of the produce() or cosume() method's loop should get false and the printing of output should continue.

But that's not happening here. So why is it getting stuck at the while loop if the condition depends on the flag variable which can only take true or false at a time ?

class Item{
    boolean valueSet = false ; 
    int item = 0 ; 

    public  void consume(){
        while(!valueSet) ;
        System.out.println("Consumed : "  + item ) ; 
        valueSet = false ;
    }

    public  void produce(int n ){
        while(valueSet);
        item = n ;
        System.out.println("Produced : "  + item ) ; 
        valueSet = true ;
    } 
}

class Producer implements Runnable{
 Item item ;
 Producer(Item itemobj){
     item = itemobj ; 
 }

 public void run(){
     while(true){
         System.out.println("\nProducing ....") ; 
     item.produce((int)Math.random()*100) ; 
     }
 }

}

class Consumer implements Runnable{
    Item item  ;
    Consumer(Item itemobj){item = itemobj ; }

    public void run(){
        while(true){
            System.out.println("\nConsuming !") ;
        item.consume() ; 

        }
    }
}


class Main{
    public static void main(String[] args) {
        Item item = new Item() ;
        Thread consumer = new Thread(new Consumer(item)) ; 
        Thread producer = new Thread(new Producer(item)) ;
        System.out.println("\nStarted producer and consumer threads : ") ; 
        consumer.start() ; 
        producer.start() ; 
    }
}

Update :

When the while(valueSet) is stuck at infinite loop in one thread , shouldn't while(!valuSet) get out of loop and flip the valueSet? This would inturn cause while(valueSet) to come out of loop right ?

As per some answers it seems when while(valueSet) is stuck , the other thread is somehow not able to access valueSet . I don't understand how this is happening. Please explain your answer.

I see that using volatile for valueSet will fix it but i am not able to understand how without using it. It's causing the infinite loop even if it depends on one flag valueSet which can't be true and false at the same time.


Solution

  • Basically, what you're trying to do here is to use valueSet as a boolean flag to synchronize the Consumer and Producer -- to make them work in turn. It is true that valueSet can only be true or false at one moment; however, it's not how the two threads (Consumer and Producer) view it.

    We know that in Java, objects are stored on the heap; that is called the main memory. For each thread, however, for performance's sake, references to used objects are kept in a thread-specific cache. As in here, Producer and Consumer share one Item object which is stored on the heap; the field item.valueSet might be cached by each thread.

     _______________    ______________  
     |   Consumer    |  |   Producer   |  
     |   _________   |  |   _________  |  
     |  |         |  |  |  |         | |  
     |  | Cache1  |  |  |  |  Cache2 | |  
     |  | valueSet|  |  |  | valueSet| |
     |  |_________|  |  |  |_________| |  
     |_______________|  |______________|
               | |              | |
               | |              | |
              _|_|______________|_|__
             |                       |
             |      MAIN MEMORY      | 
             |      valueSet         | 
             |_______________________|
    

    When, say, Consumer changes valueSet to false, it might or might not flush the new value to the main memory; similarly, when Producer checks valueSet, it might or might not try to read the newest value from the main memory. That's where the volatile keyword comes into play. When you set valueSet to be volatile, it ensures that both threads write/read the newest value to/from main memory.

    Notice that the above summary is bascially known as the JVM Memory Model. It's a set of rules to define JVM's behaviour under multithreading situations.

    If you try changing the following parts of your code:

        **volatile** boolean valueSet = false ; 
        **volatile** int item = 0 ;
        ...
        item.produce((int)(Math.random()*100)) ; // added parenthesis
    

    You will see following output:

    Started producer and consumer threads : 
    
    Consuming !
    
    Producing ....
    Produced : 83
    
    Producing ....
    Consumed : 83
    
    Consuming !
    Produced : 54
    
    Producing ....
    Consumed : 54
    
    Consuming !
    Produced : 9
    
    Producing ....
    Consumed : 9
    
    Consuming !
    Produced : 23
    
    Producing ....
    Consumed : 23