【发布时间】:2015-07-29 18:57:29
【问题描述】:
出于学习目的,我尝试实现线程安全的队列数据结构 + 消费者/生产者链,出于学习目的,我也没有使用通知/等待机制:
同步队列:
package syncpc;
/**
* Created by Administrator on 01/07/2009.
*/
public class SyncQueue {
private int val = 0;
private boolean set = false;
boolean isSet() {
return set;
}
synchronized public void enqueue(int val) {
this.val = val;
set = true;
}
synchronized public int dequeue() {
set = false;
return val;
}
}
消费者:
package syncpc;
/**
* Created by Administrator on 01/07/2009.
*/
public class Consumer implements Runnable {
SyncQueue queue;
public Consumer(SyncQueue queue, String name) {
this.queue = queue;
new Thread(this, name).start();
}
public void run() {
while(true) {
if(queue.isSet()) {
System.out.println(queue.dequeue());
}
}
}
}
制片人:
package syncpc;
import java.util.Random;
/**
* Created by Administrator on 01/07/2009.
*/
public class Producer implements Runnable {
SyncQueue queue;
public Producer(SyncQueue queue, String name) {
this.queue = queue;
new Thread(this, name).start();
}
public void run() {
Random r = new Random();
while(true) {
if(!queue.isSet()) {
queue.enqueue(r.nextInt() % 100);
}
}
}
}
主要:
import syncpcwn.*;
/**
* Created by Administrator on 27/07/2015.
*/
public class Program {
public static void main(String[] args) {
SyncQueue queue = new SyncQueue();
new Producer(queue, "PROCUDER");
new Consumer(queue, "CONSUMER");
}
}
这里的问题是,如果 isSet 方法不同步,我会得到这样的输出:
97,
55
并且程序只是继续运行而不输出任何值。而如果 isSet 方法被同步,程序可以正常工作。
我不明白为什么,没有死锁,isSet方法只是查询了set实例变量,没有设置它,所以没有竞争条件。
【问题讨论】:
-
将
volatile用于set字段以确保可见性。否则允许 CPU 将其存储在缓存/寄存器中,而不是作为优化对其进行更新。 -
工作了 thx :),另一个问题:为什么使用 synchronized 关键字,程序运行正确?同步是否强制从主内存读取变量,而不是缓存/寄存器?
-
因为
synchronized具有相同的可见性结果,这在硬件中是存储内存屏障。我建议阅读Java Concurrency in Practice进行温和的介绍。您将了解happens-before和roach motel model,而不会迷失在硬件细节(屏障、MESI 协议等)中。 -
@karim:您需要的是一个内存屏障,以确保从
set字段的读取是新鲜的(而不是从缓存中读取的)。volatile关键字会为每个读写操作插入一个内存屏障。synchronized关键字导致获取监视器锁然后释放,获取监视器也会插入内存屏障。更迂腐的是,在enqueue和dequeue中对set的写入与isSet中的读取之间需要happens-before 关系;您可以阅读 Java 语言规范以找到可以建立 happens-before 关系的所有方法。 -
此示例将通过添加
volatile或synchronized来工作,正如其他人所提到的,但通常,您希望使用同步块包围整个操作。否则,例如,如果您有 2 个消费者,则两者都可以同时检查isSet(),看看那里有一个值,然后都尝试将相同的值出列。这发生在 isSet() 和 dequeue() 之间发生线程切换的情况下,并且同步这两种方法对您没有帮助。
标签: java multithreading deadlock race-condition