使用`LinkedBlockingQueue`可能导致空指针异常
Using ` LinkedBlockingQueue` may cause null pointer exception
我最近在学习java并发编程。我知道 final
关键字可以保证安全发布。但是,我在阅读LinkedBlockingQueue
源码时,发现head
和last
字段并没有使用final
关键字。我发现在put
方法中调用了enqueue
方法,enqueue
方法直接给last.next
赋值。此时,last
可能是一个null
,因为last
没有用final
声明。我的理解正确吗?虽然lock
可以保证last
读写线程安全,但是lock
能不能保证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();
}
}
你是正确的 last
等于 node
和 null
值。然而这是故意的。 lock
只是为了保证每个线程都能正确的在这个class中进行修改。
有时使用 null
值是有意的,以表示缺少值(在这种情况下为空队列)。因为变量是private
,所以只能在class中修改,所以只要写class的人知道null
的可能性,一切都是好的。
我认为您混淆了多个不一定相互关联的不同概念。请注意,因为 last
是 private
,所以没有发布。另外head
和last
是要修饰的,所以不能是final
.
编辑
也许我误解了你的问题...
null
永远不会直接分配给 last
。因此,唯一可能发生这种情况的地方是在构造函数中,在 last
被赋值 new Node<E>(null)
之前。虽然我们可以确定构造函数在它被许多线程使用之前完成,但是对于值的可见性没有保证。
但是 put
使用 lock
确实保证了使用中的可见性。所以如果没有使用 lock
,那么 last
实际上可能是 null
.
也许您不了解 Java 的 continuous assignment
//first last is inited in the constructor
last = head = new Node<E>(null); // only the filed's value in last is null(item & next)
// enqueue
last = last.next = node;
//equals:
last.next = node;
last = last.next;
只有调用last.next
否则不会出现NPE
根据这篇博客 post https://shipilev.net/blog/2014/safe-public-construction/ 即使在构造函数中写入一个 final
属性 也足以实现安全初始化(因此您的对象将始终被发布安全)。 capacity
属性 声明为 final
.
In short, we emit a trailing barrier in three cases:
A final field was written. Notice we do not care about what field was actually written, we unconditionally emit the barrier before exiting the (initializer) method. That means if you have at least one final field write, the final fields semantics extend to every other field written in constructor.
我最近在学习java并发编程。我知道 final
关键字可以保证安全发布。但是,我在阅读LinkedBlockingQueue
源码时,发现head
和last
字段并没有使用final
关键字。我发现在put
方法中调用了enqueue
方法,enqueue
方法直接给last.next
赋值。此时,last
可能是一个null
,因为last
没有用final
声明。我的理解正确吗?虽然lock
可以保证last
读写线程安全,但是lock
能不能保证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();
}
}
你是正确的 last
等于 node
和 null
值。然而这是故意的。 lock
只是为了保证每个线程都能正确的在这个class中进行修改。
有时使用 null
值是有意的,以表示缺少值(在这种情况下为空队列)。因为变量是private
,所以只能在class中修改,所以只要写class的人知道null
的可能性,一切都是好的。
我认为您混淆了多个不一定相互关联的不同概念。请注意,因为 last
是 private
,所以没有发布。另外head
和last
是要修饰的,所以不能是final
.
编辑
也许我误解了你的问题...
null
永远不会直接分配给 last
。因此,唯一可能发生这种情况的地方是在构造函数中,在 last
被赋值 new Node<E>(null)
之前。虽然我们可以确定构造函数在它被许多线程使用之前完成,但是对于值的可见性没有保证。
但是 put
使用 lock
确实保证了使用中的可见性。所以如果没有使用 lock
,那么 last
实际上可能是 null
.
也许您不了解 Java 的 continuous assignment
//first last is inited in the constructor
last = head = new Node<E>(null); // only the filed's value in last is null(item & next)
// enqueue
last = last.next = node;
//equals:
last.next = node;
last = last.next;
只有调用last.next
否则不会出现NPE
根据这篇博客 post https://shipilev.net/blog/2014/safe-public-construction/ 即使在构造函数中写入一个 final
属性 也足以实现安全初始化(因此您的对象将始终被发布安全)。 capacity
属性 声明为 final
.
In short, we emit a trailing barrier in three cases:
A final field was written. Notice we do not care about what field was actually written, we unconditionally emit the barrier before exiting the (initializer) method. That means if you have at least one final field write, the final fields semantics extend to every other field written in constructor.