由于构造对象不正确,我听说非线程安全代码中发生了这种情况,但即使在阅读了Goetz的书中,我也没有理解这个概念。我想巩固我对这种代码气味的理解,因为我可能会这样做,甚至没有意识到。请在解释中提供代码以使其粘贴,谢谢。
答案 0 :(得分:23)
示例:在构造函数中,您创建一个事件侦听器内部类(它具有对当前对象的隐式引用),并将其注册到侦听器列表。
=>所以你的对象可以被另一个线程使用,即使它没有完成执行它的构造函数。
public class A {
private boolean isIt;
private String yesItIs;
public A() {
EventListener el = new EventListener() { ....};
StaticListeners.register(el);
isIt = true;
yesItIs = "yesItIs";
}
}
以后可能发生的另一个问题:对象A可以完全创建,可供所有线程使用,由另一个线程使用...除了该线程可以看到A实例已创建,yesItIs with it“yesItIs”价值,但不是isIt
!信不信由你,这可能发生!会发生什么:
=> 同步只是阻塞线程的一半,另一半是关于线程间可见性。
Java选择的原因是性能:如果所有数据都与所有线程共享,则线程间可见性会破坏性能,因此只保证同步数据被共享...
答案 1 :(得分:16)
非常简单的例子:
public class Test
{
private static Test lastCreatedInstance;
public Test()
{
lastCreatedInstance = this;
}
}
答案 2 :(得分:7)
这就是为什么双重检查锁定不起作用的原因。天真的代码
if(obj == null)
{
synchronized(something)
{
if (obj == null) obj = BuildObject(...);
}
}
// do something with obj
不安全,因为对局部变量的赋值可以在构造的其余部分(构造函数或工厂方法)之前发生。因此,线程1可以在BuildObject
步骤中,当线程2进入同一个块时,检测到非空obj
,然后继续对不完整的对象进行操作(线程1已经被安排在呼叫中)。
答案 3 :(得分:5)
public class MyClass{
String name;
public MyClass(String s)
{
if(s==null)
{
throw new IllegalArgumentException();
}
OtherClass.method(this);
name= s;
}
public getName(){ return name; }
}
在上面的代码中,OtherClass.method()
传递了MyClass
的实例,该实例在该点未完全构造,即尚未履行name
属性为非null的约定。
答案 4 :(得分:2)
Steve Gilham在评估为什么双重检查锁定被打破时是正确的。如果线程A进入该方法并且obj为null,则该线程将开始创建该对象的实例并将其指定为obj。线程B可能在线程A仍在实例化该对象(但未完成)时进入,然后将该对象视为非空但该对象的字段可能尚未初始化。部分构造的对象。
但是,如果允许关键字this转义构造函数,则可能出现相同类型的问题。假设您的构造函数创建一个对象的实例,该对象分叉一个线程,该对象接受您的对象类型。现在您的对象可能尚未完全初始化,即您的某些字段可能为null。您在构造函数中创建的对象对对象的引用现在可以将您引用为非null对象,但获取空字段值。
更多解释:
你的构造函数可以初始化你的类中的每个字段,但是如果在创建任何其他对象之前允许'this'转义,那么当其他线程查看时它们可以为null(或默认为primative),如果是1.它们是没有宣布最终或2.他们没有被宣布为易变的
答案 5 :(得分:0)
public class Test extends SomeUnknownClass{
public Test(){
this.addListner(new SomeEventListner(){
@Override
void act(){}
});
}
}
在此操作之后,Some Event Listener的实例将具有指向Test对象的链接,作为通常的内部类。
可在此处找到更多示例: http://www.ibm.com/developerworks/java/library/j-jtp0618/index.html