[英]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 連續賦值的理解
//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。
根據這篇博文 https://shipilev.net/blog/2014/safe-public-construction/即使在構造函數中寫入一個final
屬性也足以實現安全初始化(因此您的 object 將始終安全發布)。 並且capacity
屬性被聲明為final
。
簡而言之,我們在三種情況下發出尾隨屏障:
寫了最后一個字段。 請注意,我們並不關心實際寫入的字段,我們在退出 (initializer) 方法之前無條件地發出屏障。 這意味着如果您至少有一個最終字段寫入,則最終字段語義擴展到構造函數中寫入的所有其他字段。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.