2011-01-07 92 views
7

在我的应用程序,还有一类象下面这样:类初始化和同步类方法

public class Client { 
    public synchronized static print() { 
     System.out.println("hello"); 
    } 

    static { 
     doSomething(); // which will take some time to complete 
    } 
} 

这个类将在多线程环境中使用的,许多线程可同时调用Client.print()方法。我想知道是否有线程1触发类初始化的机会,并且在类初始化完成之前,线程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应该要求完成类初始化,并且我的一些实验支持我的猜测。但是,我在另一个环境中看到了相反的行为。有人可以帮我解释一下吗?

任何帮助表示赞赏,谢谢。

回答

4

我引用文本的理解是,(初始化之前由T中声明静态方法被调用类的初始化过程完成。

将被初始化意味着初始化过程已经开始,并已终止。

所以它应该是不可能的(据我的理解),当执行静态初始化程序时,因为线程A调用了print,另一个线程已经可以调用print

Chapter 12.4.2 JLS描述了详细的初始化过程,它负责初始化多线程环境中的类。

+0

静态初始化器是一个简单的类方法,它是在锁(类加载器的一个)下调用的。 – bestsss 2011-01-10 20:14:15

0

如果代码中一些容器如Servlet运行,你可以在容器的整个生命周期进行初始化。

3

执行被认为是类初始化的一部分,静态块:

类的初始化包括执行其静态初始化和初始化静态字段(类变量)的类中声明...

JVM规范保证这将以线程安全方式完成。引述JLS section 12.4.2 Detailed Initialization Procedure

因为Java编程语言是多线程的,一类或接口的初始化阶段需要仔细同步,因为一些其他线程试图在同一时间进行初始化同一类或接口。类或接口的初始化也可能被递归地请求作为该类或接口的初始化的一部分;例如,类A中的一个变量初始化器可能会调用一个不相关的类B的方法,该方法可能又会调用类A的方法。Java虚拟机的实现负责照顾同步和递归初始化......

更详细地,它是由类对象上获取锁实施:

用于初始化一个类或接口的步骤然后如下:

  1. 同步(§14.19)上类 对象,表示的类或接口 被初始化

你的方法static synchronized,它需要类对象上的锁,以及。由于JVM在类初始化过程中获得了相同的锁,因此一个线程不可能初始化类,并且其他方法可能会执行static synchronized方法。 所有报价均摘自 JLS 我希望这会有帮助。顺便说一句,你怎么知道打印发生在课程初始化完成之前?

编辑:其实我错了关于只有static synchronized不能与类初始化并行执行的假设。在类初始化完成之前,类上的任何方法都不能执行。

+0

@Petro,“由于JVM在类初始化过程中获得了相同的锁”,这是不准确的,因为在JLS第12.4.2节中,它说“否则,记录Class对象的初始化现在正在进行当前线程并释放对象的锁。“所以在类初始化完成之前释放类锁。 – nybon 2011-01-10 07:00:01

+0

您可以通过以下方式验证此行为:1)thread-1触发可能需要很长时间才能完成的类初始化。 2)thread-2使用同步语句来获取像synchronized(MyClass.class)这样的类锁。如果时序正常,则线程2将进入同步语句块。 – nybon 2011-01-10 07:01:57

2

如果您的“多线程环境”使用多个类加载器来加载您的客户端类,您可能会获得多个客户端实例,其中每个客户端实例都会在运行任何Client.print()之前运行静态初始化程序。调用。你会看到类似

doSomething 
hello 
doSomething 
hello 
hello 
hello 

我有一些示例代码,显示这一点,但目前的版本是有点繁琐运行。如果你想我可以清理它并发布它。

相关问题