LinkedBlockingQueue如何保证多线程环境下head和last字段被安全初始化
阅读过《java并发编程实战》的人应该知道。由于StoreStore重排序和编译器的优化作用,构造函数中的赋值语句可能会被
重排序到被构造对象引用赋值之后。这样在多线程环境下,对象被发布出去,可能得到的是一个部分初始化或者未初始化的
对象。通过给字段添加final关键字,JMM可以保证字段不被重排序到被构造对象引用赋值之后。那么现在问题来了,在阅读
LinkedBlockingQueue源码的时候,我发现head和last字段没有被final修饰(虽然说这两个字段后期需要修改不能使用final,
但是这里先讨论初始化的问题)。这里用last字段举例,我发现enqueue方法在put方法中被调用,并且enqueue方法直接将
值分配给last.next。 此时,last可能为null,因为未使用final声明last。尽管锁可以保证last读写线程的安全性,但是我觉得不能保
证last个是正确的初始值而不是null
public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {
transient Node<E> head;
private transient Node<E> last;
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
}
------分割线---------------增加两个我可能认为是证据的代码----
为了保证可见性,构造函数特加了锁
这个例子发volatile变量State赋值放到最后也是为了保证可见性。因为callable没有任何修饰,既不是final也不是volatile
----------------------分割。我想我找到了合理的解释--------------
参考这篇文章:https://shipilev.net/blog/2014/safe-public-construction/
一个final就够了
-------------分割-------------------
可能我没讲清楚,我看的源码时 jit 服务端c2编译器的优化手段,非热点代码是不会被优化的【所以跑一次的那种是看不出来的】。其次在x86下没有StoreStore重排序,所以x86平台是没有任何操作的。至于伪代码。可以看我在下面的评论。
我的结论:LinkedBlockingQueue需要自己保证安全初始化!
回答:
谢谢各位的回答。我想我在jdk源码中找到了我满意的答案:
http://hg.openjdk.java.net/jd...
java内存语义要求需要所有字段都为final才能安全初始化,不过jvm的实现取了巧。只要存在final写,就会在构造函数返回前,所有写之后放置内存屏障,就能保证所有字段安全初始化。
我看的源码时 jit 服务端c2编译器的优化手段,非热点代码是不会被优化的【所以跑一次的那种是看不出来的】。其次在x86下没有StoreStore重排序,所以x86平台是没有任何操作的。至于伪代码。可以看我在下面的评论。
我的结论:LinkedBlockingQueue需要自己保证安全初始化!
回答:
putLock不是吗?
以上是 LinkedBlockingQueue如何保证多线程环境下head和last字段被安全初始化 的全部内容, 来源链接: utcz.com/p/170829.html