我想使用以下代码跟踪getVariableAndLogAccess(RequestInfo requestInfo)
。如果只有这两个方法访问变量,它是否是线程安全的?
使线程安全的标准方法是什么?
public class MyAccessLog(){
private int recordIndex = 0;
private int variableWithAccessTracking = 42;
private final Map<Integer, RequestInfo> requestsLog = new HashMap<>();
public int getVariableAndLogAccess(RequestInfo requestInfo){
Integer myID = recordIndex++;
int variableValue = variableWithAccessTracking;
requestInfo.saveValue(variableValue);
requestLog.put(myID, requestInfo);
return variableValue;
}
public void setValueAndLog(RequestInfo requestInfo, int newValue){
Integer myID = recordIndex++;
variableWithAccessTracking = variableValue;
requestInfo.saveValue(variableValue);
requestLog.put(myID, requestInfo);
}
/*other methods*/
}
答案 0 :(得分:9)
如果只有这两个方法访问变量,它是否是线程安全的?
没有
例如,如果两个线程调用setValueAndLog
,它们最终可能会使用相同的myID
值。
使线程安全的标准方法是什么?
您应该将int
替换为AtomicInteger,使用lock或syncrhonized阻止以防止并发修改。
根据经验,使用原始变量(如前面提到的AtomicInteger
)比使用锁更好,因为锁涉及操作系统。调用操作系统就像引进律师一样 - 最好避免使用自己能解决的问题。
请注意,如果使用锁或同步块,则setter和getter都需要使用相同的锁。否则,当setter仍在更新变量时,可以访问getter,从而导致并发错误。
答案 1 :(得分:4)
如果只有这两个方法访问变量,它是否是线程安全的?
不。
直观地说,有两个原因:
增量包括读取后写入。 JLS不保证两者将作为原子操作执行。实际上,Java实现都没有这样做。
现代多核系统通过快速本地内存缓存和较慢的主内存实现内存访问。这意味着一个线程不能保证看到另一个线程的内存写入的结果......除非有适当的&#34;内存屏障&#34;强制主存储器写/读的指令。
如果内存模型表明有必要,Java将只插入这些指令。 (因为......他们放慢了代码的速度!)
从技术上讲,JLS有一整章描述Java内存模型,它提供了一组规则,允许您推理关于内存是否正确使用。对于更高级别的东西,您可以基于AtomicInteger
等提供的保证进行推理。
使线程安全的标准方法是什么?
在这种情况下,您可以使用AtomicInteger
实例,也可以使用基本对象锁定(即synchronized
关键字)或Lock
对象进行同步。
答案 2 :(得分:1)
@Malt是对的。您的代码甚至不是线程安全的。 您可以将AtomicInteger用于您的计数器,但LongAdder更适合您的情况,因为它针对您需要计算事物并且不经常读取计数结果然后更新它的情况进行了优化。 LongAdder也具有与AtomicInteger相同的线程安全保证
来自LongAdder上的java doc:
当多个线程更新用于收集统计信息但不用于细粒度同步控制的目的的公共和时,此类通常优于AtomicLong。在低更新争用下,这两个类具有相似的特征。但在高争用的情况下,这一类的预期吞吐量明显更高,但代价是空间消耗更高。
答案 3 :(得分:-4)
这是以线程安全方式登录的常用方法:
对于使用AtomicInteger counter
方法的专柜counter.addAndGet(1)
使用public synchronized void putRecord(Data data){ /**/}
添加数据
如果您只使用recordIndex
作为记录的处理程序,则可以使用同步列表替换地图:List list = Collections.synchronizedList(new LinkedList());