Java: 在线程之间以只读方式共享的实体应该是原子的

Java: should entity shared in readonly between threads be atomic

我有一个实体,每 10 秒运行 1 个单独的线程。 之前的样子:

class Bet {
   private String id;
   private Integer startingValue; // 10 dollars
   private Integer currentValue;  // 10 -> 8-> 9 -> 11
}

这个dto只是一个例子。这个分离的线程每 5 秒获取一次它,并且可以增加或减少 currentValue 的值。

目前,我将此dto 存储在ConcurentHashMap 中,它是否具有安全并发性? 我想,我应该将 startingValue 和 currentValue 存储为 AtomicInteger。完全正确吗?

因为,在多线程中的所有 dto 示例中,都是带有 final 字段的 dto,并且在构造函数中初始化,没有设置器。

我想说的是,对于 1 Bet,可能只有 1 个线程会做出一些更改。可能还有其他线程,但它们只读。

不可能出现竞争条件,因为我们只有 1 个 thead 可以更改投注。

您应该将字段从 Integer 更改为 AtomicInteger。

即使您使用 ConcurrentHashMap,您也会看到不一致的结果。尝试 运行 多次执行以下代码并查看结果。

public static void main(String[] args) throws InterruptedException {
    Map<String, BetDto> concurrentMap = new ConcurrentHashMap<>();
    concurrentMap.put("1", new BetDto("1", 1, 1));
    concurrentMap.put("2", new BetDto("2", 1, 1));

    Runnable runnable1 = () -> {
        BetDto betDto = concurrentMap.get("1");

        for (int i= 0; i < 5_00_000;i++) {
           betDto.setStartingValue(betDto.getStartingValue() + 1);
        }
    };

    Runnable runnable2 = () -> {
        BetDto betDto = concurrentMap.get("1");

        for (int i= 0; i < 5_00_000;i++) {
           betDto.setStartingValue(betDto.getStartingValue() + 1);
        }
    };

    Thread thread1 = new Thread(runnable1);
    Thread thread2 = new Thread(runnable2);
    thread1.start();
    thread2.start();
    thread1.join();
    thread2.join();
    System.out.println(concurrentMap.get("1").getStartingValue().toString());
}

注意: 当然,您可以使用 map.compute 修复上述代码,使 get and update 操作原子化并使变量可变。但我想演示如果您使用非线程安全的 API 可能会出错的事情

现在,如果您将字段更改为 AtomicInteger 和 运行。您会看到一致的结果。

class BetDto {
    private String id;
    private AtomicInteger startingValue; // 10 dollars
    private Integer currentValue;  // 10 -> 8-> 9 -> 11

    public BetDto(String id, Integer startingValue, Integer currentValue) {
        this.id = id;
        this.startingValue = new AtomicInteger(startingValue);
        this.currentValue = currentValue;
    }
    
    public AtomicInteger getStartingValue() {
        return startingValue;
    }
}

使用以下代码更改上述片段中的 for 循环:

for (int i= 0; i < 5_00_000;i++) {
     betDto.getStartingValue().getAndAdd(1);
}