簡體   English   中英

使用 `LinkedBlockingQueue` 可能會導致 null 指針異常

[英]Using ` LinkedBlockingQueue` may cause null pointer exception

最近在學習java並發編程。 我知道final關鍵字可以保證安全發布。 但是,當我閱讀LinkedBlockingQueue源碼時,發現headlast字段並沒有使用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的可能性,一切都很好。

我認為您混淆了多個不一定相關的不同概念。 請注意,因為lastprivate的,所以沒有發布。 另外headlast是要被修改的,所以它們不能是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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM