为什么这个多线程程序陷入无限循环?

时间:2018-10-08 02:06:10

标签: java multithreading performance parallel-processing producer-consumer

以下程序是一个简单的线程程序。由于某种原因,我无法弄清楚,它同时卡在两个线程中的produce()和consume()方法的无限循环中。

它会产生几次输出,然后控制台上没有输出。因此,我认为它会陷入循环。

我的问题是,由于循环取决于Item类相同对象的标志sudo rm -rf /Library/Java/JavaVirtualMachines/jdk1.8.0_80.jdk/ 的值,因此valueSet不能同时为true和false。因此,produce()或cosume()方法的循环都应该为假,并且输出应继续打印。

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

valueSet

更新:

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

6 个答案:

答案 0 :(得分:2)

对于初学者来说,您需要设置共享变量volatile

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

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

答案 1 :(得分:2)

基本上,您要在此处执行的操作是使用valueSet作为布尔标志来同步ConsumerProducer -使它们依次工作。的确,valueSet一次只能是对或错。但是,这不是两个线程(消费者和生产者)如何看待它的方式。

我们知道在Java中,对象存储在上;称为主存储器。但是,出于性能考虑,对于每个线程,对使用过的对象的引用都保留在特定于线程的缓存中。如此处所示,ProducerConsumer共享一个Item对象,该对象存储在堆中;每个线程可能会缓存字段item.valueSet

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

例如,当ConsumervalueSet更改为false时,它可能会或可能不会将新值刷新到主内存;同样,当Producer检查valueSet时,它可能会或可能不会尝试从主内存中读取最新值。这就是volatile关键字的作用。将valueSet设置为volatile时,可以确保两个线程都向/从主内存写入/读取最新值。

请注意,以上摘要基本上称为 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

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:1)

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

答案 4 :(得分:0)

这是由于Itemwhile()阻止了。

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

while(!valueSet);相同。这是一个僵局。

答案 5 :(得分:0)

您必须使用 volatile 变量“ valueSet”,因为:

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

  • 您的线程生产者和使用者正在使用变量“ valueSet”的本地副本,这使您的代码有可能同时对两个变量都是true(或false)

也:

使用 volatile 可确保

  • 您的线程生产者和消费者将直接从主内存中读取共享的易失变量“ valueSet”,这将是最新的(valueSet不可能同时为true或false)

  • 如果生产者或消费者对易失变量“ valueSet”进行,并且突然有两个线程请求读取,则可以确保 write 操作将在读取操作之前完成。