2

这对我来说可能很天真,但我总是假设下面的代码示例在 Java 中使用线程安全集合时总是可以工作并且不会因 NullPointerException 而崩溃。不幸的是,线程 t2 似乎能够在两个线程声明下方对 containsKey() 和 get() 方法的调用之间从列表中删除该项目。注释部分显示了一种无需获得 NullPointerException 即可处理此问题的方法,因为它只是测试 get() 的结果是否为 null。

我的问题是,在 Java 中使用线程安全集合处理这个问题的正确方法是什么?当然我可以使用互斥锁或同步块,但是这种方式不会破坏线程安全集合的很多好处和易用性吗?如果我必须使用互斥锁或同步块,我不能只使用非线程安全集合吗?此外,我一直听说(在学术界)检查代码的空值是不好的编程习惯。我只是疯了吗?这个问题有简单的答案吗?先感谢您。

package test;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Test {
    public static void main(String[] args) {
        final Map<Integer, Integer> test = new ConcurrentHashMap<>();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    test.put(0, 0);
                    Thread.yield();
                }
            }           
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    test.remove(0);
                    Thread.yield();
                }
            }           
        });

        t1.start();
        t2.start();

        while(true) {
            if (test.containsKey(0)) {
                Integer value = test.get(0);
                System.out.println(value);
            }
            Thread.yield();
        }   

        // OR
//      while(true) {
//          Integer value = test.get(0);
//          if (value != null) {
//              System.out.println(value);
//          }
//          Thread.yield();
//      }           
    }
}
4

4 回答 4

3

在 Java 中使用线程安全集合处理这个问题的正确方法是什么?

只执行一项操作,因此它是原子的。它也更快。

    Integer value = test.get(0);
    if (value != null) {               
         System.out.println(value);
    }

我一直听说(在学术界)检查代码的空值是不好的编程习惯。我只是疯了吗?

可能。我认为检查null, 如果一个值可以null是最佳实践。

于 2013-10-01T15:51:12.610 回答
2

if (test.containsKey(0)) {
     Integer value = test.get(0);
     System.out.println(value);
}

仍然不是原子的。在您检查containsKey.

您需要在该片段周围的共享资源上进行同步。null或者在你之后检查get

a 中的所有操作ConcurrentHashMap都是线程安全的,但它们不会超出方法边界。

于 2013-10-01T15:50:36.123 回答
2

您在滥用线程安全集合。

线程安全集合无法阻止其他代码在containsKey()和之间运行get()

相反,它们为您提供了额外的线程安全方法,这些方法将自动检查和获取元素,而不允许其他线程干扰。

这意味着您永远不应该通过基本集合接口(MapList)使用并发集合。
相反,将您的字段声明为ConcurrentMap.

在您的情况下,您可以简单地调用get(),如果找不到密钥,它将自动返回 null 。
除了在这里检查之外别无选择null。(不像更优雅的函数语言,它使用 Maybe monad)

于 2013-10-01T15:50:50.457 回答
2

此外,我一直听说(在学术界)检查代码的空值是不好的编程习惯。

使用通用 Map,当您编写Integer i = map.get(0);then if iis时null,您不能断定它0不在地图中 - 它可能存在但映射到一个null值。

但是,使用ConcurrentHashMap可以保证没有空值:

与 Hashtable 类似,但与 HashMap 不同,此类不允许将 null 用作键或值。

所以使用:

Integer i = map.get(0);
if (i != null) ...

很好。

于 2013-10-01T16:03:00.610 回答