Java 为什么这个多线程程序会陷入无限循环?
下面的程序是一个简单的线程程序。由于某种我无法理解的原因,它被困在两个线程中同时产生和使用方法的无限循环中 它生成输出几次,然后在控制台上没有输出。所以我想它会卡在环路上 我的问题是,由于循环依赖于Item类的同一对象的flag valueSet的值,valueSet不能同时为true和false。因此,product或cosume方法的循环都应该为false,并且输出的打印应该继续 但这不是在这里发生的。那么,如果条件取决于一次只能取true或false的标志变量,为什么它会卡在while循环中Java 为什么这个多线程程序会陷入无限循环?,java,multithreading,performance,parallel-processing,producer-consumer,Java,Multithreading,Performance,Parallel Processing,Producer Consumer,下面的程序是一个简单的线程程序。由于某种我无法理解的原因,它被困在两个线程中同时产生和使用方法的无限循环中 它生成输出几次,然后在控制台上没有输出。所以我想它会卡在环路上 我的问题是,由于循环依赖于Item类的同一对象的flag valueSet的值,valueSet不能同时为true和false。因此,product或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() ;
}
}
更新:
当whilevalueSet在一个线程中被卡在无限循环中时,不应该同时执行!valueSet退出循环并翻转valueSet?这会导致whilevalueSet退出循环,对吗
根据一些答案,当valueSet被卡住时,另一个线程似乎无法访问valueSet。我不明白这是怎么发生的。请解释你的答案
我知道使用volatile for valueSet可以解决这个问题,但如果不使用它,我无法理解如何解决这个问题。它会导致无限循环,即使它依赖于一个不能同时为真和假的标志值集。我可能错了,因为我不是Java专家,但请尝试这样编写您的while循环:
public void consume(){
while(!valueSet) {
System.out.println("Consumed : " + item ) ;
valueSet = false ;
}
}
像函数和for循环一样,循环需要在花括号内的wards之后包含内容。您的while循环可能会被卡住,因为它从来没有到达valueSet=false,因为它从来没有正确地启动过循环。我可能是错的,因为我不是Java专家,但请尝试这样编写您的while循环:
public void consume(){
while(!valueSet) {
System.out.println("Consumed : " + item ) ;
valueSet = false ;
}
}
像函数和for循环一样,循环需要在花括号内的wards之后包含内容。您的while循环可能会被卡住,因为它从未到达valueSet=false,因为它从未正确地启动循环。这是由于项目被while阻塞所致
如果valueSet设置为True,则whilevalueSet;将永远等待,锁定项目
暂时也一样!valueSet;。这是一个死锁。这是由于项目被while阻塞所致
如果valueSet设置为True,则whilevalueSet;将永远等待,锁定项目
暂时也一样!valueSet;。这是一个死锁。首先,您需要使该共享变量不稳定 允许一个线程将其局部变量放入寄存器,因此是的,一个线程可以将valueSet视为false,另一个线程可以将其视为true。同时使变量易失性,以强制每次从内存中读取它
但是,这不能保证代码没有其他问题。同步可能很棘手。但是,研究易失性以克服最可能的原因。首先,您需要使共享变量易失性 允许一个线程将其局部变量放入寄存器,因此是的,一个线程可以将valueSet视为false,另一个线程可以将其视为true。同时使变量易失性,以强制每次从内存中读取它
但是,这不能保证代码没有其他问题。同步可能很棘手。但是研究volatile以排除最可能的原因。U应该将value set设置为volatile以使变量对两个线程可见。U应该将value set设置为volatile以使变量对两个线程可见。基本上,这里您要做的是使用valueSet作为布尔标志来同步消费者和生产者,从而使它们依次工作。的确,valueSet在某一时刻只能为真或假;然而,这不是两个线程消费者和生产者如何看待它 我们知道在Java中,对象存储在堆上;这就是所谓的主存储器。但是,对于每个线程,出于性能考虑,对已使用对象的引用都保存在特定于线程的缓存中。在这里,生产者和消费者共享一个存储在堆上的Item对象;每个线程都可能缓存字段item.valueSet
_______________ ______________
| Consumer | | Producer |
| _________ | | _________ |
| | | | | | | |
| | Cache1 | | | | Cache2 | |
| | valueSet| | | | valueSet| |
| |_________| | | |_________| |
|_______________| |______________|
| | | |
| | | |
_|_|______________|_|__
| |
| MAIN MEMORY |
| valueSet |
|_______________________|
例如,当使用者将valueSet更改为false时,它可能会也可能不会将新值刷新到主内存中;类似地,当生产者检查valueSet时,它可能会也可能不会尝试从主存读取最新的值。这就是volatile关键字发挥作用的地方。当您将valueSet设置为volatile时,它确保两个线程都向主内存写入/读取最新的值
请注意,上面的总结基本上被称为JVM内存模型。这是一组规则来定义JVM在多线程情况下的行为
如果尝试更改代码的以下部分:
**volatile** boolean valueSet = false ;
**volatile** int item = 0 ;
...
item.produce((int)(Math.random()*100)) ; // added parenthesis
您将看到以下输出:
基本上,这里要做的是使用valueSet作为布尔标志来同步消费者和生产者,从而使它们依次工作。的确,valueSet在某一时刻只能为真或假;然而,这不是两个线程消费者和生产者如何看待它 我们知道在Java中,对象存储在堆上;这就是所谓的主存储器。但是,对于每个线程,出于性能考虑,对已使用对象的引用都保存在特定于线程的缓存中。在这里,生产者和消费者共享一个存储在堆上的Item对象;每个线程都可能缓存字段item.valueSet
_______________ ______________
| Consumer | | Producer |
| _________ | | _________ |
| | | | | | | |
| | Cache1 | | | | Cache2 | |
| | valueSet| | | | valueSet| |
| |_________| | | |_________| |
|_______________| |______________|
| | | |
| | | |
_|_|______________|_|__
| |
| MAIN MEMORY |
| valueSet |
|_______________________|
例如,当使用者将valueSet更改为false时,它可能会也可能不会将新值刷新到主内存中;类似地,当生产者检查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
您必须使用volatile变量valueSet,因为:
valueSet是生产者和消费者的共享变量
您的线程生产者和消费者正在使用变量值集的本地副本,这使得您的代码可能同时对这两个变量都是真或假
此外:
使用volatile可以确保
您的线程生产者和消费者将直接从主存中读取最新的共享易失性变量valueSet,valueSet不可能同时为真或假
如果在生产者或消费者设置的易失性变量值上发生写操作,并且任意一个线程突然请求读取,则可以保证写入操作将在读取操作之前完成
您必须使用volatile变量valueSet,因为:
valueSet是生产者和消费者的共享变量
您的线程生产者和消费者正在使用变量值集的本地副本,这使得您的代码可能同时对这两个变量都是真或假
此外:
使用volatile可以确保
您的线程生产者和消费者将直接从主存中读取最新的共享易失性变量valueSet,valueSet不可能同时为真或假
如果在生产者或消费者设置的易失性变量值上发生写操作,并且任意一个线程突然请求读取,则可以保证写入操作将在读取操作之前完成
我不是多线程Java专家,但我没有看到生产者和消费者之间有任何类型的同步。我可能会想到这里会出现死锁或活锁。查看示例以了解如何处理此类问题。使valueSet易失性。我也会考虑使用监视器锁来实现一个自由循环——你会得到更好的性能。我不是一个多线程java专家,但是我没有看到任何生产者和消费者之间的同步。我可能会想到这里会出现死锁或活锁。查看示例以了解如何处理此类问题。使valueSet易失性。我也会考虑使用一个监视器锁定在一个自由循环中-你会得到更好的性能,但是当WialEngESET被困在一个线程的无限循环中时,就不应该了!值集退出循环并翻转值集。这会导致whilevalueSet从循环中出来,对吗?但是当whilevalueSet在一个线程的无限循环中卡住时,不应该!值集退出循环并翻转值集。这会导致whilevalueSet退出循环,对吗?