簡體   English   中英

對TreeSet的並發訪問似乎不起作用

[英]Concurrent access to a TreeSet doesn't seem to work

我正在創建一個Java類IdGenerator,每次請求一個時都會分配一個唯一的整數ID。 它使用TreeSet存儲空閑ID的范圍,每次請求ID時,它會在集合中查找范圍,分配范圍中的第一個ID,刪除范圍,並添加一個較小的新范圍。 整個分配過程在集合上同步,以確保不同的線程不會發生沖突。

當我對類進行單元測試時,這工作得很好,但是我剛剛運行了一個不同類的測試,其中IdGenerator類的一個實例被快速連續十次調用,由不同的線程調用,並且每次都返回相同的值。 記錄顯示,在每次調用時,保持空閑范圍的集合具有相同的內容,盡管lastId變量不同:第一次調用時為-1,其他調用為0。 這似乎表明不同的線程正在使用該集合的不同副本,盡管這不是我對代碼的期望。

我正在使用JRE 1.8.0_191,在Windows 10上的Eclipse Neon 4.6.3中運行。

我已經嘗試在生成器對象而不是集合上進行同步,將TreeSet包裝在synchronizedSortedSet中,並使用Lock對象而不是synchronized關鍵字。 它沒有任何區別。

private final SortedSet<Range> freeRanges = new TreeSet<>();
private int lastId;


public int allocateId() throws IllegalStateException
{
    int answer;
    synchronized (freeRanges)
    {
        LOG.debug("lastId = {}, freeRanges = {}", lastId, freeRanges);
        if (freeRanges.isEmpty())
            throw new IllegalStateException("All possible IDs are allocated");
        Range range = Stream
                .of(freeRanges.tailSet(new Range(lastId + 1)), freeRanges)
                .filter(s -> !s.isEmpty())
                .map(SortedSet::first)
                .findFirst()
                .get();
        answer = lastId = range.start;
        freeRanges.remove(range);
        if (range.start != range.end)
            freeRanges.add(new Range(range.start + 1, range.end));
        LOG.debug("Allocated {}, freeRanges = {}", answer, freeRanges);
    }
    return answer;
}

日志輸出如下所示。 我希望在第n次調用時,分配的數字是n-1,並且更新自由范圍的集合以顯示從n開始到100結束的范圍。但是,我看到的是:

16:03:18.554 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = -1, freeRanges = [Range [start=0, end=100]]
16:03:18.570 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - lastId = 0, freeRanges = [Range [start=0, end=100]]
16:03:18.586 [main] DEBUG uk.org.thehickses.idgenerator.IdGenerator - Allocated 0, freeRanges = [Range [start=1, end=100]]

謝謝所有回復的人。 我擔心我可能會誤導你 - 我意識到,昨晚騎車回家時,問題與從不同線程多次調用allocateId方法無關。 我發布的日志清楚地表明所有這些調用都是在同一個線程中進行的(稱為“main”)。

今天早上騎自行車上班時,我意識到問題所在 - 它是其他線程正在調用freeId方法,該方法將ID返回給freeRanges集。 特別是,每次調用allocateId的ID在下次調用allocateId 之前被釋放。 這就解釋了為什么每次調用allocateId freeRanges都有相同的內容。

我做了一個簡單的更改,以確保在發生這種情況時,如果找到的范圍包含lastId + 1則表示分配的值,即使它不在范圍的開頭。 當然,如果它不在該范圍的開頭,則范圍將在freeRanges替換為最多兩個新范圍 - 一個包含范圍內小於分配數量的所有數字,另一個包含所有這些范圍這不僅僅是它。 這可以確保我們盡可能循環遍歷所有可用的數字,並且只有當沒有大於最后分配的空閑數字時,我們才會回到開頭。

修改后的代碼如下。 顯然,我應該花更多的時間在我的自行車上,而不是在我的電腦前!

public int allocateId() throws IllegalStateException
{
    int answer;
    synchronized (freeRanges)
    {
        LOG.debug("Allocating: lastId = {}, freeRanges = {}", lastId, freeRanges);
        if (freeRanges.isEmpty())
            throw new IllegalStateException("All possible IDs are allocated");
        int nextId = lastId + 1;
        Range range = Stream
                .of(freeRanges.tailSet(new Range(nextId)), freeRanges)
                .filter(s -> !s.isEmpty())
                .map(SortedSet::first)
                .findFirst()
                .get();
        answer = lastId = range.contains(nextId) ? nextId : range.start;
        freeRanges.remove(range);
        range.splitAround(answer).forEach(freeRanges::add);
        LOG.debug("Allocated {}, freeRanges = {}", answer, freeRanges);
    }
    return answer;
}

暫無
暫無

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

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