繁体   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