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);
}
我有一个实体,每 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);
}