以下程序是一个简单的线程程序。由于某种原因,我无法弄清楚,它同时卡在两个线程中的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
,也会导致无限循环。
答案 0 :(得分:2)
对于初学者来说,您需要设置共享变量volatile
。
允许一个线程将其局部变量放在寄存器中,因此,是的,一个线程可以将valueSet
视为错误,而另一个线程可以将其视为true
。同时。使变量volatile
强制每次从内存中读取它。
但是,这不能保证代码没有其他问题。同步可能很棘手。但是研究volatile
来克服最可能的原因。
答案 1 :(得分:2)
基本上,您要在此处执行的操作是使用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内存模型。这是定义多线程情况下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)
这是由于Item
被while()
阻止了。
如果将valueSet
设置为True
,则while(valueSet);
将永远等待,并锁定Item
与while(!valueSet);
相同。这是一个僵局。
答案 5 :(得分:0)
您必须使用 volatile 变量“ valueSet”,因为:
“ valueSet”是生产者和消费者的共享变量
您的线程生产者和使用者正在使用变量“ valueSet”的本地副本,这使您的代码有可能同时对两个变量都是true(或false)
也:
使用 volatile 可确保
您的线程生产者和消费者将直接从主内存中读取共享的易失变量“ valueSet”,这将是最新的(valueSet不可能同时为true或false)
如果生产者或消费者对易失变量“ valueSet”进行写,并且突然有两个线程请求读取,则可以确保 write 操作将在读取操作之前完成。