在我的应用程序中,有一个类如下:
public class Client {
public synchronized static print() {
System.out.println("hello");
}
static {
doSomething(); // which will take some time to complete
}
}
此类将在多线程环境中使用,许多线程可以同时调用Client.print()方法。我想知道thread-1是否有可能触发类初始化,并且在类初始化完成之前,thread-2进入print方法并打印出“hello”字符串?
我在生产系统(64位JVM + Windows 2008R2)中看到了这种行为,但是,我无法在任何环境中使用简单程序重现此行为。
在Java语言规范的第12.4.1节(http://java.sun.com/docs/books/jls/second_edition/html/execution.doc.html)中,它说:
类或接口类型T将在第一次出现以下任何一个之前立即初始化:
- T是一个类,创建了一个T实例。
- T是一个类,调用T声明的静态方法。
- 分配由T声明的静态字段。
- 使用由T声明的静态字段,对字段的引用不是编译时常量(第15.28节)。编译时常量的引用必须在编译时解析为编译时常量值的副本,因此使用这样的字段永远不会导致初始化。
根据这一段,类初始化将在静态方法的调用之前进行,但是,在调用静态方法之前,不清楚类初始化是否需要完成 。根据我的直觉,JVM应该在进入静态方法之前完成类初始化,并且我的一些实验支持我的猜测。但是,我确实在另一个环境中看到了相反的行为。有人能否对我有所了解?
感谢任何帮助,谢谢。
答案 0 :(得分:4)
我对引用文本的理解是,在调用T声明的静态方法之前,类初始化过程已完成(将 初始化)
将被初始化意味着初始化过程已经开始并已终止。
因此,(我的理解)不应该可以执行静态初始化程序,因为线程A调用了print
,而另一个线程已经可以调用print
。
Chapter 12.4.2描述了详细的初始化过程,它负责在多线程环境中初始化类。
答案 1 :(得分:3)
执行被认为是类初始化一部分的静态块:
类的初始化包括执行其静态初始化器和类中声明的静态字段(类变量)的初始化器...
JVM规范保证以线程安全的方式完成。引用JLS section 12.4.2 Detailed Initialization Procedure:
由于Java编程语言是多线程的,因此初始化类或接口需要仔细同步,因为其他一些线程可能正在尝试同时初始化同一个类或接口。作为该类或接口的初始化的一部分,还可以递归地请求类或接口的初始化;例如,类A中的变量初始化程序可能会调用不相关的类B的方法,而该方法又可能调用类A的方法.Java虚拟机的实现负责处理同步和递归初始化...
更详细地说,它是通过获取Class对象的锁来实现的:
初始化类或接口的过程如下:
- 同步(第14.19节) 表示类或的对象 要初始化的界面
醇>
您的方法是static synchronized
,它也需要锁定Class对象。由于在类初始化期间JVM获取了相同的锁,因此一个线程无法初始化类以及其他执行static synchronized
方法。
所有报价均来自
JLS
我希望这很有帮助。顺便说一下,你怎么知道在类初始化完成之前打印?
编辑:实际上我错误地认为只有static synchronized
无法与类初始化并行执行。在类初始化完成之前,无法执行类上的任何方法。
答案 2 :(得分:3)
如果您的“多线程环境”使用多个类加载器来加载您的Client类,您可能会获得多个Client实例,每个实例都会在运行任何Client.print()调用之前运行静态初始化器。你会看到像
这样的东西doSomething
hello
doSomething
hello
hello
hello
我有一些示例代码显示了这一点,但当前版本运行起来有点繁琐。如果你想我可以清理它并发布它。
答案 3 :(得分:0)
如果代码在某些容器(例如Servlet)中运行,则可以在容器的生命周期中初始化它。