繁体   English   中英

Java发生之前和线程安全

[英]Java Happens-Before and Thread Safety

假设我有一个包装HashMap的类,如下所示:

public final class MyClass{

     private final Map<String, String> map;

     //Called by Thread1
     public MyClass( int size ){
         this.map = new HashMap<String, String>( size );
     }

     //Only ever called by Thread2
     public final String put( String key, String val ){
        return map.put( key, value );
     }

     //Only ever called by Thread2
     public final String get( String key ){
        return map.get( key );
     }

     //Only ever called by Thread2
     public final void printMap(  ){
       //Format and print the contents of the map
     }

}

该类通过“Thread1”初始化。 但是,put,get,printMap和其他操作只能由“Thread2”调用。

我是否正确理解这个类是线程安全的:

  1. 由于对map的引用被声明为final,所有其他线程都会看到map的初始状态(之前发生)。

  2. 由于put / get / printMap / etc仅由Thread2调用,因此不需要互斥。

谢谢

所以你要问的是正确的假设。 如果可以保证只使用这种方式,则不需要使其成为线程安全的。 你不能在java中传递一半构造的对象,因此“构造函数可能无法完成”是不可能的。

所以,如果你这样做

new Thread(new Runnable() {

    public void run() {
          final MyClass instance = new MyClass(10);
          new Thread(new Runnable() {
               public void run() {
                    instance.put("one", "one");
                      ....
               }

           }).start();
     }

}).start();

你很好:)这就是你所描述的,由Thread1创建但仅由Thread2使用。 线程无法与自身发生冲突。

线程安全是一个不同的定义,其中组合实体可以安全地与多个线程交互。 在您描述的情况下,该场景不会发生,因为您基本上有一个构造的线程和另一个操作的线程。

回答这个问题有点棘手,因为JLS不包含类是线程安全的概念 - 所有它指定的是操作之间的关系(例如,对字段的写入,对字段的读取等)。

也就是说,根据大多数定义,此类不是线程安全的 - 它包含由非线程安全Map的非同步访问引起的数据争用。 但是,您对它的使用线程安全的,因为您在构造之后安全地将this.map发布到线程2,并且此时this.map只能被一个线程访问,在这种情况下线程安全不是问题。

换句话说,这仅仅是在询问HashMap在一个线程内创建和访问时是否是线程安全的时候稍微复杂一些。 在这种情况下的答案是HashMap 不是线程安全的,但它不需要。

同样,你的类不是线程安全的,但听起来可能不需要。

如果你坚持你将要发生的事情的定义那么它实际上是线程安全的 也就是说,只有thread-2才能从Map中获取和获取。

由于Map被声明为final,因此您确实建立了与thread-1的写入和thread-2的读取相关的before-before关系。

a)由于对map的引用被声明为final,所有其他线程都会看到map的初始状态(之前发生)。

是。 指定size的空HashMap。

b)因为只有Thread2调用了put / get / printMap / etc,所以不需要相互排斥。

是的,虽然这种逻辑通常会让我在实​​践中感到害怕:)

这里的问题不是关于类实现本身,而是关于你描述的类的操作是否是线程安全的,所以类实现并不重要。

我可以想到Thread2访问MyClass实例的两种可能方式。

  1. Thread1和Thread2都是独立运行的。 在这种情况下,它不是线程安全的,因为Thread2可以在Thread1中运行的构造函数完成之前调用put / get / printMap / etc,从而产生NPE。 Thread2访问的MyClass实例可以为null,具体取决于Thread2如何访问它。 如果它是一个共享实例,当Thread2访问它时,如果MyClass构造函数尚未在Thread1中执行,则它可以为null。
  2. Thread1创建实例并将此实例传递给Thread2。 在这种情况下,它是线程安全的,但这实际上是无关紧要的,因为没有多线程访问此实例,因为Thread2必须等待实例传递。

因此,在您描述的情况下,答案实际上不取决于类实现,而是取决于其实例的共享方式。 在最坏的情况下(方式1),它不是线程安全的。

暂无
暂无

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

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