java线程安全:它是线程安全的吗?

java线程安全:它是线程安全的吗?,java,multithreading,Java,Multithreading,我正在实践中阅读java并发。有一些问题,我不明白。 比如说, package com.thread; import java.util.Collections; import java.util.HashSet; import java.util.Random; import java.util.Set; public class HiddenIterator { private final Set<Integer> set = Collections.synchron

我正在实践中阅读java并发。有一些问题,我不明白。 比如说,

package com.thread;

import java.util.Collections;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class HiddenIterator {
    private final Set<Integer> set = Collections.synchronizedSet(new HashSet<Integer>());
    public void add(Integer i) {
        synchronized (set) {
            set.add(i);
        }
    }
    public void remove(Integer i) {
        synchronized (set) {
            set.remove(i);
        }
    }
    public void addTenThings() {
        Random random = new Random();
        for (int i = 0; i < 10; ++i) {
            add(random.nextInt());
        }
        //Hidden Iterator!  
        System.out.println("DEBUG: added ten elements to " + set);
    }
}
package.com.thread;
导入java.util.Collections;
导入java.util.HashSet;
导入java.util.Random;
导入java.util.Set;
公共类消隐器{
private final Set=Collections.synchronizedSet(new HashSet());
公共void add(整数i){
已同步(集){
增加(i);
}
}
公共无效删除(整数i){
已同步(集){
设置。移除(i);
}
}
公共无效添加内容(){
随机=新随机();
对于(int i=0;i<10;++i){
添加(random.nextInt());
}
//隐藏迭代器!
System.out.println(“调试:在“+集合”中添加了十个元素);
}
}
程序线程安全吗?
如果不是,如何编辑?

您对集合的某些访问是线程安全的:对
添加
删除
的调用是同步的,因此它们不能同时运行

但是,在构建消息时,末尾的
System.out
行将调用集合上的
toString
,并且
toString
必须遍历集合的元素。尽管您使用了
synchronizedSet
,但它只保护对单个元素的访问,在迭代过程中不会保持集合不变。如果其他线程在您的
System.out
行运行时添加和删除元素,则消息中将显示哪些数字是不可预测的。您需要在该行周围设置一个
synchronized
块,以便在生成消息时“冻结”集合的内容

请注意,只有单个的
add
调用是同步的,因此其他线程可以看到正在添加的单个项之间的集合。这意味着其他线程可能只看到十项中的一部分。根据您的程序使用列表的目的,这可能是问题,也可能不是问题

如果需要以原子方式添加这十个元素,以便其他线程可以看到它们全部或全部,那么可以在
addTenThings
方法中的循环周围放置一个
synchronized

您不需要同时使用
集合.synchronizedSet
synchronized
块。一个或另一个都可以。区别在于:

  • Collections.synchronizedSet
    保护对集合的所有访问,因此您不会忘记在需要的地方进行同步。但是,它只能保护集合上的单个方法调用。特别是,在集合上迭代会导致不可预测的结果,因为在循环运行时,其他线程可以添加和删除项
  • synchronized
    块可以保护集合上的多个方法调用,以便它们充当原子操作-但它们只保护其他
    synchronized
    块,因此必须记住对访问集合的所有代码使用
    synchronized

    • 有些太安全,有些不够安全。您不需要在
      add()
      remove()
      内显式同步,因为这是由
      synchronizedSet
      包装器自动完成的


      但是,您确实需要围绕
      println()
      语句进行同步,因为当您连接
      set
      时,它会隐式调用
      set.toString()
      ,该函数在内部迭代其元素(“隐藏迭代器”),如果没有显式同步,这是不安全的,正如在.

      中所解释的,不要忘记,
      System.out.println
      将通过
      toString
      对集合进行迭代,这意味着它可能也应该同步。哎呀,我没有注意到这一点。抢手货这个答案提出了一个重要的观点。许多人,包括一些地方的官方Javadocs,将具有同步方法的类型称为“线程安全”,但并不总是准确的。如果只存在方法同步,通常会有一些方法以线程不安全的方式使用该类型。@LewBloch我发现文档通常使用准确的术语,并且在存在限制时会小心地指出它们。(
      Vector
      真是一个惊喜。)我发现这本书是一本很好的指南。@D.B.不如JCIP…@shmosel也许没有,但总的来说,如果有人发现一本资源令人困惑或难以理解,那么阅读另一本可能是个好主意。对一个人有意义的事情对另一个人可能没有意义。