java多线程中的安全构造
这是在java多线程安全构造实践的背景下进行的。我正在读JCIP的书,有人能在下面解释一下吗: 对象只有在其 构造函数返回,因此从其内部发布对象 构造函数可以发布未完全构造的对象。这是 即使发布是中的最后一条语句,也为true 构造器 在这里,我想了解特定部分“即使出版物是 构造函数中的最后一条语句 我知道从构造函数启动线程是不安全的,但当它是构造函数中的最后一条语句时,它也是不安全的吗 我的猜测是,为什么它不安全是因为JVM可以对语句进行重新排序,而最后一条语句将不再是最后一条。请对此发表评论或更正我的理解 编辑:我所说的启动线程的意思是泄漏java多线程中的安全构造,java,multithreading,Java,Multithreading,这是在java多线程安全构造实践的背景下进行的。我正在读JCIP的书,有人能在下面解释一下吗: 对象只有在其 构造函数返回,因此从其内部发布对象 构造函数可以发布未完全构造的对象。这是 即使发布是中的最后一条语句,也为true 构造器 在这里,我想了解特定部分“即使出版物是 构造函数中的最后一条语句 我知道从构造函数启动线程是不安全的,但当它是构造函数中的最后一条语句时,它也是不安全的吗 我的猜测是,为什么它不安全是因为JVM可以对语句进行重新排序,而最后一条语句将不再是最后一条。请对此发表评论
这个引用。这种泄漏可能通过多种方式发生,如发布事件或线程开始。例如
class MyClass {
public MyClass() {
publish();
}
void publish() {
System.out.println((consistent() ? "" : "in") + "consistent object");
}
protected boolean consistent() {
return true;
}
}
class Child extends MyClass {
int result;
public Child() {
super();
result = 42;
}
protected boolean consistent() {
return result == 42;
}
}
即使它是构造函数中的最后一个调用,在考虑继承时也可能导致混乱的情况。下面是一些示例代码来演示该行为
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String... args) {
A a = new B();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + ": " + a);
}
public static void doSomethingWithA(final A a) {
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread() + ": " + a);
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class A {
public A() {
Test.doSomethingWithA(this);
}
@Override
protected void finalize() throws Throwable {
if (false) {
System.out.println(MESSAGE);
}
}
}
class B extends A {
boolean isConstructed = false;
public B() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.isConstructed = true;
}
@Override
public String toString() {
return ( "B is " + (!this.isConstructed ? "not yet " : "")
+ "fully constructed.");
}
}
现在,想象一下A
和B
是由两个不同的开发人员开发的。尽管A
的开发人员泄漏了premature对象作为A
构造函数中的最后一条语句,但它不是B
构造函数中的最后一条语句(记住每个构造函数开头的隐式super()
)。因此,在doSomethingWithA(…)
中启动的线程可以访问早熟对象。这就是为什么您应该只在构造函数中调用自己类的final
方法的原因。如需了解更多信息,我建议
A类
的设计存在固有缺陷,因为构造函数中的此
存在先发制人的泄漏,请参阅以获取更多详细信息。这里有两种见解可以帮助您。首先,构造函数(大部分)与其他方法一样,用于同步;也就是说,它本身不提供任何功能(除非如下所述)。第二,线程安全总是在单个操作之间
假设您有以下构造函数:
MyClass() {
this.i = 123;
MyClass.someStaticInstance = this; // publish the object
}
// and then somewhere else:
int value = MyClass.someStaticInstance.i;
问题是:最后一个表达式能做什么
- 如果尚未设置
someStaticInstance
,它可能会抛出NullPointerException
- 它还可能导致
value==123
- 但有趣的是,它也会导致
value==0
最后一点的原因是动作可以重新排序,构造函数在这方面并不特殊。让我们更仔细地看看所涉及的动作:
- A.为新实例分配空间,并将其所有字段设置为默认值(0表示
inti
)
- B.设置
.i=123
- C.设置
someStaticInstance=
- D.读取
someStaticInstance
,然后读取其i
如果您对其重新排序,您可以得到:
- A.为新实例分配空间,并将其所有字段设置为默认值(0表示
inti
)
- C.设置
someStaticInstance=
- D.读取
someStaticInstance
,然后读取其i
- B.设置
.i=123
这就是它--值
是0,而不是123
JCIP还警告您,泄漏可能以微妙的方式发生。例如,假设您没有显式设置someStaticInstance
字段,而只是调用someListener.register(this)
。您仍然泄漏了引用,您应该假设您注册的侦听器可能会使用它做一些危险的事情,例如将其分配给someStaticInstance
即使i
字段是最终字段,这也是正确的。您可以从最终字段中获得一些线程安全性,但前提是您没有从构造函数中泄漏this
。具体地说,在中,它说:
当一个对象的构造函数完成时,它被认为是完全初始化的。只有在对象完全初始化后才能看到该对象引用的线程才能确保看到该对象最终字段的正确初始化值
保证“仅在对象完全初始化后才能看到对该对象的引用”的唯一方法是不从其构造函数泄漏引用。否则,您可以想象一个线程正在读取MyClass.someStaticInstance
,此时该字段刚刚设置完毕,但JVM尚未识别构造函数已完成。如果泄漏一个过早的对象,即调用构造函数中另一个对象的方法,并将此作为参数,则不安全。被调用的方法已经访问了传递的引用,但被引用的对象可能尚未完全构造。我理解了这一部分,我的问题是,如果它是最后一条语句,该怎么办。我的推理正确吗?也许你正在构造一个从你当前所在的类派生的对象。在这种情况下,最终的对象还没有完全构造,甚至在构造函数中的最后一条语句之后也是如此。这不适用于你给出的例子。看到@yshavit了吗?好吧,现在想想看——这是一件好事,否则到处都是用户引用;-)JCIP中讨论的问题是,内存模型对线程在部分构造的对象中看到的内容没有做出相同的保证。例如,您可以设置最后一个字段,然后泄漏此字段