Почему эта многопоточная программа застревает в бесконечном цикле? - PullRequest
0 голосов
/ 08 октября 2018

Данная программа представляет собой простую многопоточную программу.По какой-то причине, которую я не могу понять, он застревает в бесконечном цикле обоих методов yield () и потребление () одновременно в обоих потоках.

Он производит вывод несколько раз, а затемнет вывода на консоль.Поэтому я предполагаю, что он застревает в цикле.

Мой вопрос такой, поскольку цикл зависит от значения флага valueSet одного и того же объекта класса Item, valueSet не может быть одновременно истинными ложь одновременно.Таким образом, цикл либо метода yield (), либо cosume () должен получить значение false и печать вывода должна продолжиться.

Но этого здесь не происходит.Так почему же он застревает в цикле while, если условие зависит от переменной flag, которая может принимать только true или false одновременно?

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() ; 
    }
}

Обновление:

Когда while(valueSet) застрял в бесконечном цикле в одном потоке, while(!valuSet) не должно выйти из цикла и перевернутьvalueSet?Это может привести к тому, что while(valueSet) выйдет из цикла, верно?

Согласно некоторым ответам кажется, что когда while(valueSet) застревает, другой поток как-то не может получить доступ к valueSet. Я не понимаю, как это происходит.Пожалуйста, объясните свой ответ.

Я вижу, что использование volatile для valueSet исправит это, но я не могу понять, как без него.Это вызывает бесконечный цикл, даже если он зависит от одного флага valueSet, который не может быть истинным и ложным одновременно.

Ответы [ 6 ]

0 голосов
/ 08 октября 2018

Вы должны использовать volatile переменную "valueSet", потому что:

  • "valueSet" является общей переменной для производителя и потребителя

  • Ваши потоки Producer & Consumer используют локальную копию вашей переменной "valueSet", что дает вашему коду возможность того, что он может быть истинным (ИЛИ ложным) для обоих одновременно

Также:

Использование volatile гарантирует, что

  • ваши потоки Производитель и Потребитель прочитают общую переменную переменную"valueSet" непосредственно из основной памяти, которая будет последней (нет возможности того, чтобы valueSet был истинным или ложным одновременно)

  • Если происходит запись ваша переменная переменная "valueSet" от производителя или потребителя, и вдруг любой из ваших потоков запрашивает чтение, гарантируется, что операция write будет завершена до операции чтения.

0 голосов
/ 08 октября 2018

По сути, здесь вы пытаетесь использовать valueSet в качестве логического флага для синхронизации Consumer и Producer - чтобы они работали по очереди.Это правда, что valueSet может быть истинным или ложным только в один момент;однако, это не то, как его видят два потока (Потребитель и Производитель).

Мы знаем, что в Java объекты хранятся в куче ;это называется основной памятью .Однако для каждого потока в целях производительности ссылки на используемые объекты хранятся в специфичном для потока кэше .Как и здесь, Producer и Consumer совместно используют один Item объект, который хранится в куче;поле item.valueSet может кэшироваться каждым потоком.

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

Когда, скажем, Consumer меняет valueSet на false, оно может сбрасывать или не сбрасывать новое значение в основную память;аналогично, когда Producer проверяет valueSet, он может или не может пытаться прочитать новейшее значение из основной памяти.Вот тут и вступает в игру ключевое слово volatile.Когда вы устанавливаете valueSet равным volatile, это гарантирует, что оба потока записывают / читают новейшее значение в / из основной памяти.

Обратите внимание, что вышеупомянутая сводка в основном известна как JVM MemoryМодель .Это набор правил для определения поведения JVM в ситуациях с многопоточностью.

Если вы попытаетесь изменить следующие части вашего кода:

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

Вы увидите следующий вывод:

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
0 голосов
/ 08 октября 2018

U должен установить valueSet на volatile, чтобы сделать переменную видимой для двух потоков.

0 голосов
/ 08 октября 2018

Для начала вам нужно сделать эту общую переменную volatile.

Потоку разрешено помещать свои локальные переменные в регистр, поэтому да, один поток может видеть valueSet как ложное идругой может видеть это как true.В то же время.Сделайте переменную volatile, чтобы она каждый раз считывалась из памяти.

Однако это не гарантирует отсутствия других проблем с кодом.Синхронизация может быть сложной.Но исследуйте volatile, чтобы преодолеть наиболее вероятную причину.

0 голосов
/ 08 октября 2018

Это происходит из-за блокировки Item с помощью while().

Если valueSet установлен на True, тогда while(valueSet); будет ждать бесконечно, блокируя Item

То же самое для while(!valueSet);.Это тупик.

0 голосов
/ 08 октября 2018

Я могу ошибаться, так как я не эксперт в Java, но попробуйте написать цикл while следующим образом:

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

Циклы нуждаются в содержимом после скобок внутри фигурных скобок, как функции и циклы forделать.Ваш цикл while может застрять, потому что он никогда не достигает valueSet = false, потому что он никогда не запускает цикл должным образом.

...