Bri*_*ian 19 java concurrency multithreading constructor
有人告诉我,Java构造函数是同步的,因此在构造期间无法同时访问它,我想知道:如果我有一个构造函数将对象存储在一个映射中,另一个线程在构造之前从该映射中检索它完成后,该线程会阻塞,直到构造函数完成?
让我演示一些代码:
public class Test {
private static final Map<Integer, Test> testsById =
Collections.synchronizedMap(new HashMap<>());
private static final AtomicInteger atomicIdGenerator = new AtomicInteger();
private final int id;
public Test() {
this.id = atomicIdGenerator.getAndIncrement();
testsById.put(this.id, this);
// Some lengthy operation to fully initialize this object
}
public static Test getTestById(int id) {
return testsById.get(id);
}
}
Run Code Online (Sandbox Code Playgroud)
假设put/get是地图上唯一的操作,所以我不会通过迭代之类的东西获得CME,并试图忽略其他明显的缺陷.
我想知道的是,如果另一个线程(显然不是那个构造对象的线程)尝试使用getTestById并调用它上面的东西来访问该对象,它会阻塞吗?换一种说法:
Test test = getTestById(someId);
test.doSomething(); // Does this line block until the constructor is done?
Run Code Online (Sandbox Code Playgroud)
我只是想弄清楚构造函数同步在Java中走了多远,以及像这样的代码是否有问题.我最近看过像这样的代码,而不是使用静态工厂方法,我想知道这在多线程系统中是多么危险(或安全).
Gra*_*ray 23
有人告诉我,Java构造函数是同步的,因此在构造期间不能同时访问它
当然不是这种情况.与构造函数没有隐含的同步.多个构造函数不仅可以同时发生,而且可以通过例如在构造函数内部分配this正在构造的引用来获得并发问题.
如果我有一个构造函数将对象存储在一个映射中,另一个线程在构造完成之前从该映射中检索它,那么该线程是否会阻塞直到构造函数完成?
不,不会.
线程应用程序中构造函数的一个大问题是,编译器在Java内存模型下具有对构造函数内部操作进行重新排序的权限,以便在创建对象引用(构造函数完成)之后进行. final字段将保证在构造函数完成时完全初始化,而不是其他"正常"字段.
在你的情况下,因为你正在Test进入synchronized-map 然后继续进行初始化,正如@Tim所提到的,这将允许其他线程以可能半初始化的状态获得对象的支持.一种解决方案是使用static方法来创建对象:
private Test() {
this.id = atomicIdGenerator.getAndIncrement();
// Some lengthy operation to fully initialize this object
}
public static Test createTest() {
Test test = new Test();
// this put to a synchronized map forces a happens-before of Test constructor
testsById.put(test.id, test);
return test;
}
Run Code Online (Sandbox Code Playgroud)
我的示例代码可以工作,因为您正在处理一个synchronized-map,它会调用synchronized它来确保Test构造函数已经完成并且已经进行了内存同步.
您的示例中的大问题是"发生在之前"保证(构造函数可能在Test放入映射之前未完成)和内存同步(构造线程和获取线程可能会看到Test实例的不同内存).如果移动put构造函数的外部,则两者都由synchronized-map处理.它是什么对象synchronized 来保证构造函数在放入映射并且内存已经同步之前已经完成并不重要.
我相信,如果你叫testsById.put(this.id, this);的非常构造函数结束时,你可以在实践中是好的但是这不是好的形式,并在至少需要小心注释/文档.如果类被子类化并且在子类之后完成初始化,这将无法解决问题super().static我展示的解决方案是一个更好的模式.
use*_*421 13
有人告诉我,Java构造函数是同步的
"有人在某处"被严重误导.构造函数不同步.证明:
public class A
{
public A() throws InterruptedException
{
wait();
}
public static void main(String[] args) throws Exception
{
A a = new A();
}
}
Run Code Online (Sandbox Code Playgroud)
这段代码抛出java.lang.IllegalMonitorStateException了wait()电话.如果同步有效,则不会.
它甚至没有意义.它们不需要同步.构造函数只能在new(),定义后按每个调用new()返回一个不同的值来调用.因此,两个线程同时使用相同的值调用构造函数是不可能的this.因此不需要构造函数的同步.
如果我有一个构造函数将对象存储在一个映射中,另一个线程在构造完成之前从该映射中检索它,那么该线程是否会阻塞直到构造函数完成?
不,为什么会这样呢?谁会阻止它?让'this'从这样的构造函数中逃脱是不好的做法:它允许其他线程访问仍在构造中的对象.
Tim*_*der 12
你被误导了.您所描述的内容实际上被称为不正确的发布,并在Java Concurrency In Practice一书中进行了详细讨论.
所以,是的,另一个线程可以获得对象的引用,并在完成初始化之前开始尝试使用它.但等等,考虑到这个答案会变得更糟:https://stackoverflow.com/a/2624784/122207 ...基本上可以重新排序引用赋值和构造函数完成.在引用的示例中,一个线程可以在新实例上分配h = new Holder(i)和另一个线程调用h.assertSanity(),其时间正确,n以便为在Holder构造函数中分配的成员获取两个不同的值.