HastSet <string>线程安全

时间:2018-02-24 00:06:55

标签: java multithreading concurrency thread-safety hashset

===更新====

来自评论

所以我清楚地阅读了文档,并且知道它不是线程安全的,我想进行一个小实验,看看它会如何破解。所以医生说结果是非确定性的。有谁知道会发生什么?如果我想证明它不是线程安全的,我怎么能编写一个示例代码,这样我才能真正看到它没有线程安全?你们真的试过,看过没有工作的例子吗?你有示例代码吗?

如果我有三个线程访问string的hashset。

  • 添加新字符串
  • 第二次删除字符串
  • 第三次删除所有

HashSet线程安全吗?

public void test()
{
    Set<String> test = new HashSet<>();
    Thread t0= new Thread(new Runnable() {
        @Override
        public void run() {
            while (true) {
                boolean c = test.contains("test");
                System.out.println("checking " + c);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            while (true) {
                test.add("test");
                System.out.println("adding");
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            while (true) {
                if (!test.isEmpty())
                {
                    test.removeAll(test);
                }
                System.out.println("removing");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    });
    t0.start();
    t1.start();
    t2.start();

    while(true) {

    }
}

我有这个测试代码并运行它似乎有效。没有例外被抛出。我有点困惑,因为HashSet不是线程安全的。 我错过了什么?

2 个答案:

答案 0 :(得分:1)

来自comment

  

所以我清楚地阅读了文档,并且知道它不是线程安全的,我想进行一个小实验,看看它会如何破解。所以医生说结果是非确定性的。有谁知道会发生什么?如果我想证明它不是线程我是如何编写示例代码的,这样我才能真正看到它没有线程安全?你们真的试过并看到那些不起作用的例子吗?你有示例代码吗?

问题是更新Set可能不是原子操作,尤其是当需要重新调整内部哈希表时。

如果两个线程同时更新,您可能会得到一个线程覆盖另一个线程更改的简单结果,因此您将丢失更改。更严重的是,冲突可能腐败 Set的内部结构。

为了说明这一点,这是一个小程序,在添加值时会导致高冲突。添加的所有值都是不同的,因此它们都应该被添加,但是当程序完成时,您将看到Set的大小不正确,证明某些附加值丢失了。

final int THREAD_COUNT = 10;
final int NUMS_TO_ADD = 100000;
Set<Integer> set = new HashSet<>();
Thread[] threads = new Thread[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i++) {
    final int threadNo = i;
    threads[i] = new Thread() {
        @Override public void run() {
            for (int j = 0; j < NUMS_TO_ADD; j++)
                set.add(j * THREAD_COUNT + threadNo); // all distinct values
        }
    };
    threads[i].start();
}
for (int i = 0; i < threads.length; i++)
    threads[i].join();
System.out.println("Found " + set.size() + " values, expected " + THREAD_COUNT * NUMS_TO_ADD);

每次运行时,您都会得到不同的结果,例如

Found 898070 values, expected 1000000
Found 825773 values, expected 1000000
Found 731886 values, expected 1000000
Exception in thread "Thread-7" java.lang.ClassCastException: java.base/java.util.HashMap$Node cannot be cast to java.base/java.util.HashMap$TreeNode
    at java.base/java.util.HashMap$TreeNode.moveRootToFront(HashMap.java:1883)
    at java.base/java.util.HashMap$TreeNode.putTreeVal(HashMap.java:2063)
    at java.base/java.util.HashMap.putVal(HashMap.java:638)
    at java.base/java.util.HashMap.put(HashMap.java:612)
    at java.base/java.util.HashSet.add(HashSet.java:220)
    at Test$1.run(Test.java:16)

或者程序只是挂起!

答案 1 :(得分:0)

线程安全

线程不安全并不意味着您不能在多踏板中使用它,否则程序会抛出异常。这意味着在多线程中执行程序时,您无法始终获得所需内容。有关详情,请参阅this

  

在计算机编程中,线程安全描述程序部分或   可以从多个编程线程调用的例程   线程之间不需要的交互。

而且,即使您获得了预期的实验结果,也不能说对象是线程安全的。因为结果可能在不同的环境中有所不同。您应该使用JDK提供的同步机制。

HashSet的

HashSet不是线程安全的,这意味着:

  • 如果您在其中写入对象,则此对象可能不可见 其他主题。
  • 如果您同时从不同的线程中读取该集合,它们可能会得到不同的结果。
  • 如果您先拨打add,请拨打removeAll,其中包含对象 设置可能无法删除。
  • ......
用户Andreas的例子非常清楚。由于HashSet基于HashMap的密钥集,因此您可以参考此How to prove that HashMap in java is not thread-safe

解决方案

JDK提供了一个线程安全版本set,该集合上的所有操作都需要首先获取内部监视器锁。

Set s = Collections.synchronizedSet(new HashSet(...));