繁体   English   中英

集<String>线程安全

[英]HastSet<String> thread safe

===更新====

来自评论

所以我清楚地阅读了文档并且知道它不是线程安全的,我想运行一个小实验来看看它会如何崩溃。 所以文档说结果是不确定的。 有谁知道会发生什么? 如果我想证明它不是线程安全的,我该如何编写示例代码以便我实际上可以看到它不是线程安全的? 你们真的尝试过并看到不起作用的例子吗? 你有示例代码吗?

如果我有三个线程访问字符串的哈希集。

  • 一个添加一个新字符串
  • 第二次删除字符串
  • 第三次全部删除

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 不是线程安全的。 为什么我失踪了?

来自评论

所以我清楚地阅读了文档并且知道它不是线程安全的,我想运行一个小实验来看看它会如何崩溃。 所以文档说结果是不确定的。 有谁知道会发生什么? 如果我想证明它不是线程,我如何编写示例代码以便我实际上可以看到它不是线程安全的? 你们真的尝试过并看到那个不起作用的例子吗? 你有示例代码吗?

问题是更新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)

或者程序只是挂起!

线程安全

线程不安全并不意味着您不能在多线程中使用它,否则程序会抛出异常。 这意味着当程序在多线程中执行时,您不能总是得到您想要的。 请参阅了解更多信息。

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

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

哈希集

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

  • 如果你向其中写入一个对象,这个对象可能对其他线程不可见。
  • 如果您同时从不同线程读取该集合,它们可能会得到不同的结果。
  • 如果先调用add ,然后调用removeAll ,则该集合中的对象可能不会被删除。
  • ......

用户 Andreas 的例子非常清楚。 由于HashSet是基于HashMap的键集,可以参考这个How to proof that HashMap in java is not thread-safe

解决方案

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM