簡體   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