如何修复get / check / out的非原子使用?

时间:2014-10-02 18:25:28

标签: java multithreading thread-safety atomic static-analysis

我有一个JSONArray,我正在迭代填充我的Map,如下所示。我的ppJsonArray会有这样的数据 -

[693,694,695,696,697,698,699,700,701,702]

下面是我的代码,因为我的静态分析工具抱怨了线程安全问题 -

Map<Integer, Integer> m = new HashMap<Integer, Integer>();
ConcurrentMap<String, Map<Integer, Integer>> partitionsToNodeMap = new ConcurrentHashMap<String, Map<Integer, Integer>>();
int hostNum = 2;

JSONArray ppJsonArray = j.getJSONArray("pp");
for (int i = 0; i < ppJsonArray.length(); i++) {
    m.put(Integer.parseInt(ppJsonArray.get(i).toString()), hostNum);
}
Map<Integer, Integer> tempMap = partitionsToNodeMap.get("PRIMARY");
if (tempMap != null) {
    tempMap.putAll(m);
} else {
    tempMap = m;
}
partitionsToNodeMap.put("PRIMARY", tempMap);

但是当我运行静态分析工具时,它正在抱怨 -

Non-atomic use of get/check/put on partitionsToNodeMap.put("PRIMARY", tempMap)

这让我觉得上面的代码不是线程安全的?我该如何解决这个问题?

2 个答案:

答案 0 :(得分:1)

上面的代码不是线程安全的。

是否需要线程安全? (即,多个线程是否使用了partitionsToNodeMap?多个线程可以运行此例程吗?还是可以在线程B运行此例程时线程更新一个线程更新partitionsToNodeMap的其他例程?)

如果对这些问题的回答都是“是”,那么您可能需要使用某种同步。


partitionsToNodeMap是ConcurrentHashMap。如果地图结构本身一次由多个线程更新,那么这将阻止地图结构本身变得腐败;但地图中的数据可能不仅仅是随机字符串和整数。它可能意味着你的程序。地图结构本身受到保护免受腐败这一事实不会阻止地图内容的高级含义变得腐败。


  

您能提供一个示例,我该如何保护它?

不完整,因为线程安全是整个程序的属性。你不能逐个功能地进行线程安全。

线程安全就是保护不变量。不变量是关于数据的断言,必须始终为真。例如,如果你在建立一个大富翁游戏,一个不变量会说游戏中的总金额必须总是15,140美元。

如果大富翁游戏中的某个线程通过从一个玩家手中拿走X美元并将其返还给银行来处理付款,那么这是一个两步骤,在这两个步骤之间,不变量已经< / em>的。如果第一个线程在两个步骤之间被抢占,而另一个线程计算了游戏中的所有钱,则会得到错误的总数。

Java synchronized关键字的主要用例(或者等效地,对于java.util.concurrent.locks.ReentrantLock类)是为了防止其他线程看到破坏的不变量。

任何一种锁定方式都是自愿。要使其工作,您必须包装可以暂时破坏受保护块中的不变量的每个代码块

synchronized(bank-lock) {
    deductNDollarsFrom(N, player);
    giveNDollarsTo(N, bank);
}
AND 关心关于不变量的每个代码块也必须包含在受保护的块中。

synchronized(bank-lock) {
    int totalDollars = countAllMoneyInGame(...);
    if (totalDollars != 15140) {
        throw new CheatingDetectedException(...);
    }
}

Java不会让余额转移和审核同时发生,因为它永远不会允许两个线程同时在同一个对象(在这种情况下为bank-lock)上进行同步。

你必须弄清楚你的不变量是什么。静态分析器告诉你get()... put()序列看起来像一块可能关注关于不变量的代码块。你必须弄清楚它是否确实存在。是否有其他线程可以在get()和put()之间做些什么可能导致事情向南移动?如果是这样,那么两个代码块应该在同一个对象上同步,这样它们就不能同时执行。

答案 1 :(得分:0)

您的静态分析工具很困惑,因为您所做的事情看起来像是一种典型的竞争条件。

Map<Integer, Integer> tempMap = partitionsToNodeMap.get("PRIMARY");  // GET
if (tempMap != null) {  // CHECK
    tempMap.putAll(m);
} else {
    tempMap = m;
}
partitionsToNodeMap.put("PRIMARY", tempMap);  // PUT

如果在分配partitionsToNodeMap.put("PRIMARY");后另一个帖子到tempMap,则会覆盖其他线程的工作。在众多其他潜在的坏事中。看起来你没有多个线程访问它,所以这不是问题。但是,它将更清楚地表达为:

Map<Integer, Integer> primaryMap = partitionsToNodeMap.get("PRIMARY");
if (primaryMap != null) {
    primaryMap.putAll(m);
} else {
    partitionsToNodeMap.put("PRIMARY", m);
}

如果您想让静态分析工具满意,请将您的并发地图替换为常规地图。您提供的代码不需要线程安全的数据结构。