为什么这个多线程程序会陷入死循环?

Why is this multithreaded program getting stuck at infinite loop?

下面的程序是一个简单的线程程序。由于某种我无法理解的原因,它在两个线程中同时陷入了 produce() 和 consume() 方法的无限循环。

它产生了几次输出,然后控制台没有输出。所以我认为它会卡在循环中。

我的问题是,由于循环取决于项目 class 的同一对象的标志 valueSet 的值,因此 valueSet 不能同时为真和假同一时间 。因此,produce() 或 cosume() 方法的循环中的任何一个都应该为 false,输出的打印应该继续。

但这不会发生在这里。那么,如果条件取决于一次只能取 true 或 false 的标志变量,为什么它会卡在 while 循环中呢?

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,它也会导致无限循环。

我可能是错的,因为我不是 Java 方面的专家,但请尝试像这样编写 while 循环:

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

循环需要大括号内 wards 后面的内容,就像函数和 for 循环一样。您的 while 循环 可能 被卡住,因为它永远不会到达 valueSet = false 因为它实际上从未正确启动循环。

这是由于 Itemwhile() 阻止了。

如果 valueSet 设置为 True 那么 while(valueSet); 将永远等待,锁定 Item

while(!valueSet); 相同。这是一个死锁。

对于初学者,您需要创建共享变量 volatile

允许线程将其局部变量放入寄存器中,所以是的,一个线程可以将 valueSet 视为 false,而另一个线程可以将其视为 true。同时。使变量volatile每次都强制从内存中读取。

但是,这并不能保证代码没有其他问题。同步可能很棘手。但研究 volatile 以排除最可能的原因。

U 应该将 valueSet 设置为 volatile 以使变量对两个线程可见。

基本上,您在这里尝试做的是使用 valueSet 作为布尔标志来同步 ConsumerProducer -- 让它们轮流工作。确实valueSet只能在某一时刻为真或为假;但是,这不是两个线程(消费者和生产者)如何看待它。

我们知道在Java中,对象存储在上;这就是所谓的 主内存 。然而,对于每个线程,为了性能起见,对使用对象的引用保存在线程特定的 缓存 中。在这里,ProducerConsumer 共享一个存储在堆上的 Item 对象;字段 item.valueSet 可能被每个线程缓存。

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

例如,当 ConsumervalueSet 更改为 false 时,它可能会也可能不会将新值刷新到主内存;类似地,当 Producer 检查 valueSet 时,它可能会也可能不会尝试从主内存中读取最新值。这就是 volatile 关键字发挥作用的地方。当您将 valueSet 设置为 volatile 时,它确保两个线程 write/read 最新值 to/from 主内存。

请注意,上面的摘要基本上称为 JVM 内存模型。它是一组规则,用于定义 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

您必须使用 volatile 变量 "valueSet" 因为:

  • "valueSet"是生产者和消费者的共享变量

  • 您的线程生产者和消费者正在使用变量的本地副本 "valueSet",这使您的代码有可能同时对两者为真(或假)

另外:

使用volatile确保

  • 您的线程生产者和消费者将 读取 共享易失性变量 "valueSet" 直接从主内存中获取最新信息(valueSet 不可能为真或同时为false)

  • 如果生产者或消费者对您的易失性变量 "valueSet" 进行了 写入 操作,并且您的任何一个突然请求读取线程,保证 write 操作将在读取操作之前完成。