代码之家  ›  专栏  ›  技术社区  ›  Jörn Horstmann

是ConcurrentHashMap中的赋值。computeIfAbsent线程安全?

  •  3
  • Jörn Horstmann  · 技术社区  · 6 年前

    考虑以下某种固定大小缓存的实现,它允许通过整数句柄进行查找:

    static class HandleCache {
        private final AtomicInteger counter = new AtomicInteger();
        private final Map<Data, Integer> handles = new ConcurrentHashMap<>();
        private final Data[] array = new Data[100_000];
    
        int getHandle(Data data) {
            return handles.computeIfAbsent(data, k -> {
                int i = counter.getAndIncrement();
                if (i >= array.length) {
                    throw new IllegalStateException("array overflow");
                }
                array[i] = data;
                return i;
            });
    
        }
    
        Data getData(int handle) {
            return array[handle];
        }
    }
    

    compute函数中有一个数组存储,它以任何方式都不同步。java内存模型是否允许其他线程稍后从该数组中读取空值?

    PS: 如果id从 getHandle 存储在 final 字段,并且仅从其他线程通过此字段访问?

    3 回复  |  直到 6 年前
        1
  •  1
  •   Peter Lawrey    6 年前

    读取访问不是线程安全的。您可以间接地使其线程安全,但它可能很脆弱。我将以一种更简单的方式实现它,并且只有在后来证明它存在性能问题时才对其进行优化。e、 因为你在一个真实测试的分析器中看到它。

    static class HandleCache {
        private final Map<Data, Integer> handles = new HashMap<>();
        private final List<Data> dataByIndex = new ArrayList<>();
    
        synchronized int getHandle(Data data) {
            Integer id = handles.get(data);
            if (id == null) {
                 id = handles.size();
                 handles.put(data, id);
                 dataByIndex.add(id);
            }
            return id;
        }
    
        synchronized Data getData(int handle) {
            return dataByIndex.get(handle);
        }
    }
    
        2
  •  1
  •   David Soroko    6 年前

    假设您确定从的值读取的数组的索引 counter 是-您可能会得到一个空读数

    最简单的示例(还有其他示例)如下:

    T1呼叫 getHandle(data) 之后就被停职了 int i = counter.getAndIncrement(); T2呼叫 handles[counter.get()] 并读取null。

    您应该能够通过战略部署的 sleep 和两条螺纹。

        3
  •  0
  •   Jacob G.    6 年前

    来自以下文件: ConcurrentHashMap#computeIfAbsent :

    整个方法调用是以原子方式执行的,因此每个键最多应用一次函数。在计算过程中,其他线程在此映射上尝试的某些更新操作可能会被阻止,因此计算应该简短,并且不得尝试更新此映射的任何其他映射。

    文档中提到的阻塞仅指 Map ,因此如果任何其他线程尝试访问 array 直接(而不是通过 地图 ),可以有比赛条件和 null 可以读取。