簡體   English   中英

Java線程意外行為

[英]Java Threading Unexpected Behavior

我們一直在研究線程錯誤,但不確定這是怎么回事。 以下是我們代碼中的最小化示例。 有一個緩存保存從數據庫中檢索的數據(或者:“一個冗長的同步操作”,就這個例子而言)。 有一個用於重新加載緩存的線程,而其他線程嘗試查詢緩存。 有一段時間緩存為空,等待重新加載。 在此期間它不應該是可查詢的,我們試圖通過同步訪問緩存的方法來強制執行此操作 - 包括讀取和寫入。 然而,如果你運行這個類一段時間,你將在search()獲得NPE。 這怎么可能?

Java文檔聲明“同一個對象上的兩個同步方法的調用不可能交錯。當一個線程正在為一個對象執行同步方法時,所有其他線程為同一個對象塊調用同步方法(暫停執行)直到第一個線程完成對象“。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CacheMultithreading01 {
    private long dt = 1000L;

    public static void main(String[] args) {
        CacheMultithreading01 cm = new CacheMultithreading01();
        cm.demonstrateProblem();
    }

    void demonstrateProblem() {
        QueryableCache cache = new QueryableCache();
        runInLoop("Reload", new Runnable() {
            @Override
            public void run() {
                cache.reload();
            }
        });
        runInLoop("Search", new Runnable() {
            @Override
            public void run() {
                cache.search(2);
            }
        });
        // If the third "runInLoop" is commented out, no NPEs
        runInLoop("_Clear", new Runnable() {
            @Override
            public void run() {
                cache.clear();
            }
        });
    }

    void runInLoop(String threadName, Runnable r) {
        new Thread(new Runnable() {
            @Override
            public synchronized void run() {
                while (true) {
                    try {
                        r.run();
                    } catch (Exception e) {
                        log("Error");
                        e.printStackTrace();
                    }
                }
            }
        }, threadName).start();
    }

    void log(String s) {
        System.out.format("%d %s %s\n", System.currentTimeMillis(), Thread
                .currentThread().getName(), s);
    }

    class QueryableCache {
        private List<Integer> cache = new ArrayList<>();

        public synchronized void reload() {
            clear();
            slowOp(); // simulate retrieval from database
            cache = new ArrayList<>(Arrays.asList(1, 2, 3));
        }

        public synchronized void clear() {
            cache = null;
        }

        public synchronized Integer search(Integer element) {
            if (cache.contains(element))
                return element;
            else
                return null;
        }

        private void slowOp() {
            try {
                Thread.sleep(dt);
            } catch (InterruptedException e) {
            }
        }
    }
}
//java.lang.NullPointerException
//at examples.multithreading.cache.CacheMultithreading01$QueryableCache.search(CacheMultithreading01.java:73)
//at examples.multithreading.cache.CacheMultithreading01$2.run(CacheMultithreading01.java:26)
//at examples.multithreading.cache.CacheMultithreading01$4.run(CacheMultithreading01.java:44)
//at java.lang.Thread.run(Thread.java:745)

我們不明白為什么即使代碼同步也會發生NPE。 如果我們注釋掉第三次調用runInLoop (執行cache.clear ),我們也不明白為什么NPE會停止發生。 我們還嘗試使用ReentrantReadWriteLock實現鎖定 - 結果是一樣的。

如果cache為空,則必須檢入search方法。 否則,在以前在clear -method中將cache設置為null的情況下,在search調用contains NullPointerException

由於您沒有任何更高級的鎖定,因此可以連續調用clear()search() 這顯然會導致NPE。

調用reload()search()不會導致問題,因為在重新加載時,緩存會在同步塊內被清除,然后重建,從而阻止在其間執行其他(搜索)操作。

為什么有一個clear()方法會使cache處於“壞”狀態( search()甚至不檢查)?

“有一段時間緩存為空,等待重新加載。” 這是你的問題: clear將items設置為null,然后返回,釋放同步鎖,允許其他人訪問。 最好將“新”賦值設為原子而不是clear()

假設slowOp()需要返回緩存的數據( private List<Integer> slowOp()) ,則在分配數據之前檢索該數據

ArrayList<Integer> waitingForData = slowOp(); cache = watingForData; 只有在數據可用后才會“更新”緩存。 賦值是一個原子操作,在更新引用時沒有任何東西可以訪問緩存

同步正常工作。

問題是clear方法將cache放到null 並且無法保證在search之前調用reload方法。

另外,請注意該方法reload ,它不會釋放鎖定。 因此,當您等待slowOp完成時,其他方法無法執行。

三個不同的線程調用高速緩存的clear()search()和reload()而沒有明確的交錯。 由於交錯不保​​證為clear()和search()線程獲取鎖定序列,因此搜索線程可能有機會在clear()線程之后獲取對象的鎖定。 在這種情況下,搜索將導致NullPointerException。

您可能必須在搜索對象中檢查等於null的緩存,並且可以在search()方法中執行reload()。 這將保證搜索結果或返回null(如果適用)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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