假设我有一个包装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”调用。
我是否理解这个类是线程安全的:
由于对地图的引用被声明为final,因此所有其他线程都会看到地图的初始状态(发生之前已经建立)。
由于put / get / printMap / etc仅由Thread2调用,因此不需要互斥。
谢谢
答案 0 :(得分: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使用。线程无法与自身发生冲突。
Thread-Safe是一个不同的定义,其中组合实体可以安全地与多个线程进行交互。在你描述的情况下,这种情况不会发生,因为你基本上有一个构造的线程和另一个操作的线程。
答案 1 :(得分:4)
回答这个问题有点棘手,因为JLS不包含类是线程安全的概念 - 所有它指定的是操作之间的关系(例如,对字段的写入,字段的读取等) )。
也就是说,根据大多数定义,此类不是线程安全的 - 它包含由对非线程安全Map的非同步访问引起的数据争用。但是,您对的使用是线程安全的,因为您在构造之后安全地将this.map
发布到线程2,并且此时this.map
只能被一个线程访问,在这种情况下,线程安全不是问题。
换句话说,这只是在询问HashMap
是否在一个线程内创建和访问时是否是线程安全的时候稍微涉及一些。在这种情况下的答案是HashMap
不线程安全,但它不需要。
同样,你的类不是线程安全的,但听起来可能不需要。
答案 2 :(得分:0)
如果你坚持你的定义,那么实际上是线程安全的。也就是说,只有thread-2才能从Map中获取和获取。
由于Map被声明为final,所以你确实建立了一个关于thread-1的写入和thread-2读取的before-before关系。
a)由于对map的引用被声明为final,所有其他线程 会看到地图的初始状态(发生之前是 建立)。
是。指定了size
的空HashMap。
b)由于put / get / printMap / etc仅由Thread2调用,因此 不需要互相排斥。
是的,虽然这种逻辑通常会让我在实践中感到害怕:)
答案 3 :(得分:-1)
这里的问题不是关于类实现本身,而是关于你描述的类的操作是否是线程安全的,所以类的实现并不重要。
我可以想到Thread2访问MyClass
实例的两种可能方式。
MyClass
实例可以为null,具体取决于Thread2如何访问它。如果它是一个共享实例,当Thread2访问它时,如果MyClass
构造函数尚未在Thread1中执行,则它可以为null。因此,在您描述的情况下,答案实际上不取决于类的实现,而是取决于它的实例是如何共享的。在最坏的情况下(方式1),它不是线程安全的。