10

假设我有一个包装 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. 由于对映射的引用被声明为最终状态,所有其他线程将看到映射的初始状态(已建立之前发生)。

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

谢谢

4

4 回答 4

9

所以你要问的是正确的假设。如果您可以保证它仅以这种方式使用,则无需使其成为线程安全的。你不能在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 使用。线程无法与自身发生碰撞。

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

于 2013-12-20T18:33:30.023 回答
4

这有点难以回答,因为 JLS 不包含线程安全类的概念——它指定的只是动作之间的关系(例如,写入字段、读取字段等)。

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

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

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

于 2013-12-20T19:01:58.630 回答
0

如果你坚持你对将会发生什么的定义,那么它实际上是线程安全的。也就是说,只有线程 2 会从 Map 中放置和获取。

由于 Map 被声明为 final,因此您确实建立了关于线程 1 的写入和线程 2 的读取的发生前关系。

a) 由于对映射的引用被声明为最终状态,所有其他线程将看到映射的初始状态(已建立之前发生)。

是的。指定的空 HashMap size

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

是的,虽然这种类型的逻辑在实践中通常让我害怕 :)

于 2013-12-20T18:39:28.693 回答
-1

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

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

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

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

于 2013-12-20T18:25:43.537 回答