为什么这个多线程程序会陷入死循环?
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 因为它实际上从未正确启动循环。
这是由于 Item
被 while()
阻止了。
如果 valueSet
设置为 True
那么 while(valueSet);
将永远等待,锁定 Item
while(!valueSet);
相同。这是一个死锁。
对于初学者,您需要创建共享变量 volatile
。
允许线程将其局部变量放入寄存器中,所以是的,一个线程可以将 valueSet
视为 false,而另一个线程可以将其视为 true
。同时。使变量volatile
每次都强制从内存中读取。
但是,这并不能保证代码没有其他问题。同步可能很棘手。但研究 volatile
以排除最可能的原因。
U 应该将 valueSet
设置为 volatile
以使变量对两个线程可见。
基本上,您在这里尝试做的是使用 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
时,它确保两个线程 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 操作将在读取操作之前完成。
下面的程序是一个简单的线程程序。由于某种我无法理解的原因,它在两个线程中同时陷入了 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 因为它实际上从未正确启动循环。
这是由于 Item
被 while()
阻止了。
如果 valueSet
设置为 True
那么 while(valueSet);
将永远等待,锁定 Item
while(!valueSet);
相同。这是一个死锁。
对于初学者,您需要创建共享变量 volatile
。
允许线程将其局部变量放入寄存器中,所以是的,一个线程可以将 valueSet
视为 false,而另一个线程可以将其视为 true
。同时。使变量volatile
每次都强制从内存中读取。
但是,这并不能保证代码没有其他问题。同步可能很棘手。但研究 volatile
以排除最可能的原因。
U 应该将 valueSet
设置为 volatile
以使变量对两个线程可见。
基本上,您在这里尝试做的是使用 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
时,它确保两个线程 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 操作将在读取操作之前完成。