类初始化和同步类方法

时间:2011-01-07 07:41:29

标签: java multithreading concurrency synchronization

在我的应用程序中,有一个类如下:

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应该在进入静态方法之前完成类初始化,并且我的一些实验支持我的猜测。但是,我确实在另一个环境中看到了相反的行为。有人能否对我有所了解?

感谢任何帮助,谢谢。

4 个答案:

答案 0 :(得分:4)

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

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

因此,(我的理解)不应该可以执行静态初始化程序,因为线程A调用了print,而另一个线程已经可以调用print

JLS的

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

答案 1 :(得分:3)

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

  

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

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

  

由于Java编程语言是多线程的,因此初始化类或接口需要仔细同步,因为其他一些线程可能正在尝试同时初始化同一个类或接口。作为该类或接口的初始化的一部分,还可以递归地请求类或接口的初始化;例如,类A中的变量初始化程序可能会调用不相关的类B的方法,而该方法又可能调用类A的方法.Java虚拟机的实现负责处理同步和递归初始化...

更详细地说,它是通过获取Class对象的锁来实现的:

  

初始化类或接口的过程如下:

     
      
  1. 同步(第14.19节)   表示类或的对象   要初始化的界面
  2.   

您的方法是static synchronized,它也需要锁定Class对象。由于在类初始化期间JVM获取了相同的锁,因此一个线程无法初始化类以及其他执行static synchronized方法。 所有报价均来自 JLS 我希望这很有帮助。顺便说一下,你怎么知道在类初始化完成之前打印?

编辑:实际上我错误地认为只有static synchronized无法与类初始化并行执行。在类初始化完成之前,无法执行类上的任何方法。

答案 2 :(得分:3)

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

这样的东西
doSomething
hello
doSomething
hello
hello
hello

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

答案 3 :(得分:0)

如果代码在某些容器(例如Servlet)中运行,则可以在容器的生命周期中初始化它。