===更新====
来自评论
所以我清楚地阅读了文档,并且知道它不是线程安全的,我想进行一个小实验,看看它会如何破解。所以医生说结果是非确定性的。有谁知道会发生什么?如果我想证明它不是线程安全的,我怎么能编写一个示例代码,这样我才能真正看到它没有线程安全?你们真的试过,看过没有工作的例子吗?你有示例代码吗?
如果我有三个线程访问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不是线程安全的。 我错过了什么?
答案 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不是线程安全的,这意味着:
add
,请拨打removeAll
,其中包含对象
设置可能无法删除。HashSet
基于HashMap
的密钥集,因此您可以参考此How to prove that HashMap in java is not thread-safe。
JDK提供了一个线程安全版本set
,该集合上的所有操作都需要首先获取内部监视器锁。
Set s = Collections.synchronizedSet(new HashSet(...));