1. static块执行时机
java static块在类被初始化的时候被执行。
参考《深入Java虚拟机》中的描述,一个java class的生命周期:
- 装载
通过类的全限定名,产生一个代表该类型的二进制数据流;
解析这个二进制数据流为方法区内的数据结构;
创建一个表示该类型的java.lang.Class的实例。
如果一个类装载器在预先装载的时遇到缺失或错误的class文件,它需要等到程序首次主动使用该类时才报告错误。
- 连接
验证,确认类型符合Java语言的语义,检查各个类之间的二进制兼容性(比如final的类不用拥有子类等 ),另外还需要进行符号引用的验证;
准备,Java虚拟机为类变量分配内存,设置默认初始值;
解析(可选的 ) ,在类型的常量池中寻找类,接口,字段和方法的符号引用,把这些符号引用替换成直接引用的过程。
- 初始化
当一个类被主动使用时,Java虚拟就会对其初始化,如下六种情况为主动使用:
- 当创建某个类的新实例时(如通过new或者反射,克隆,反序列化等)
- 当调用某个类的静态方法时
- 当使用某个类或接口的静态字段时
- 当调用Java API中的某些反射方法时,比如类Class中的方法,或者java.lang.reflect中的类的方法时
- 当初始化某个子类时
- 当虚拟机启动某个被标明为启动类的类(即包含main方法的那个类)
Java编译器会收集所有的类变量初始化语句和类型的静态初始化器,将这些放到一个特殊的方法中:clinit。
static块可以使用下述实例验证:
public abstract class AbstractTestStatic {
static int count;
static {
System. out .println("AbstractTestStatic static block" );
count = 1;
}
public static int getCount(){
System. out .println("AbstractTestStatic getCount" );
return count ;
}
}
public class TestStatic extends AbstractTestStatic{
static {
System. out .println("TestStatic static block" );
count = 2;
}
public static int getCount() {
System. out .println("TestStatic getCount" );
return count;
}
}
public class Main1 {
public static void main(String[] args) {
Class[] classArray = new Class[1];
classArray [0] = TestStatic. class;
}
}
classArray [0] = TestStatic. class; 这个语句会引起类的装载和连接,但不会初始化。运行程序,可以看到,静态块没有被执行。
2. java static块在一个classloader中只会执行一次
同一个classloader中验证较为简单
不同的classloader中的验证方法如下:
public class DynamicClassLoader extends ClassLoader {
public DynamicClassLoader(ClassLoader parent ) {
super (parent );
}
@SuppressWarnings ("unchecked" )
public Class loadClass(String classPath , String className ) throws ClassNotFoundException {
try {
String url = classPathParser(classPath ) + classNameParser(className );
System. out .println(url );
URL myUrl = new URL( url);
URLConnection connection = myUrl .openConnection();
InputStream input = connection .getInputStream();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int data = input.read();
while (data != -1) {
buffer .write(data );
data = input .read();
}
input .close();
byte [] classData = buffer.toByteArray();
return defineClass(noSuffix(className ), classData, 0, classData .length );
} catch (MalformedURLException e ) {
e.printStackTrace();
} catch (IOException e ) {
e.printStackTrace();
}
return null ;
}
private String pathParser(String path ) {
return path .replaceAll( "\\\\", "/" );
}
private String classPathParser(String path ) {
String classPath = pathParser(path );
if (!classPath .startsWith( "file:")) {
classPath = "file:" + classPath;
}
if (!classPath .endsWith( "/")) {
classPath = classPath + "/";
}
return classPath ;
}
private String classNameParser(String className ) {
return className .substring(0, className .lastIndexOf("." )).replaceAll( "\\.", "/" )
+ className .substring(className .lastIndexOf( "."));
}
private String noSuffix(String className ) {
return className .substring(0, className.lastIndexOf( "." ));
}
}
public class AClass {
static {
System. out .println("static in AClass" );
}
}
public class Main2 {
@SuppressWarnings ("rawtypes" )
public static void main(String[] args)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
DynamicClassLoader acl = new DynamicClassLoader(DynamicClassLoader. class.getClassLoader());
Class s1 = acl .loadClass("D:/workspaces/workspace-rabbit/MultiThread/target/classes" ,"com.vip.test.MultiThread.ch1.thread.staticorder.AClass.class" );
s1.newInstance();
DynamicClassLoader bcl = new DynamicClassLoader(DynamicClassLoader. class.getClassLoader());
Class s2 = bcl .loadClass("D:/workspaces/workspace-rabbit/MultiThread/target/classes" ,"com.vip.test.MultiThread.ch1.thread.staticorder.AClass.class" );
s2.newInstance();
}
}
3. java static块执行顺序
java static块的执行顺序是按照父类静态块->子类静态块,一个类内部的静态块按照定义顺序执行。
public class Test {
public static int i;
static {
i = 10;
}
public static void main(String[] args) {
}
static {
i = 20;
}
}
经过编译后,效果和下面的代码一致:
public class Test {
public static int _i;
public static void main(String[] args) {
}
static {
i = 10;
i = 20;
}
}
4. java static块执行时多线程是安全的,但同一线程内不能保证安全
多线程安全性可以用下面的例子看出:
public class BClass {
static Integer count;
static BClass instance;
static {
System. out .println("Bclass statc block" );
try {
TimeUnit. SECONDS .sleep(10);
} catch (InterruptedException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
}
count = new Integer(0);
instance = new BClass();
}
static BClass getInstance(){
return instance;
}
public Integer getCount(){
return count ;
}
}
public class Main3 {
public static void main(String[] args) {
Thread threads [] = new Thread[2];
for (int i = 0; i < 2; i++) {
threads [i ] = new Thread() {
@Override
public void run() {
System. out .printf("Thread : %d started\n" , Thread.currentThread().getId());
BClass bc = BClass. getInstance();
System. out .printf("Thread : %d getcount\n", Thread. currentThread().getId());
System. out .printf("Thread : %d getcount %d \n", Thread. currentThread().getId(), bc.getCount());
}
};
threads [i ].start();
try {
TimeUnit. SECONDS .sleep(5);
} catch (InterruptedException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for (int i = 0; i < 2; i++) {
try {
threads [i ].join();
} catch (InterruptedException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
执行后,输出:
Thread : 13 started
Bclass statc block
Thread : 14 started 在这里thread14会阻塞直到13的getInstance执行完成后,才继续执行。
Thread : 13 getcount
Thread : 14 getcount
Thread : 14 getcount 0
Thread : 13 getcount 0
单线程不安全的问题如下面这个例子,在static块中,又使用handler,触发static方法的执行。此时,JVM并不会阻塞同一个线程,从而引发NPE。
public class CfgLoader {
ConfigContext newContext = new ConfigContext();
ConfigContext oldContext = new ConfigContext();
public ConfigContext initConfig(IConfigChangedHandler handler) {
ConfigContext newContext = new ConfigContext();
handler .handle(oldContext , newContext );
return newContext ;
}
}
public class ConfigContext {
int count = 0;
public int getCount() {
return count ;
}
public void setCount( int count ) {
this .count = count;
}
}
public interface IConfigChangedHandler {
void handle(ConfigContext previous , ConfigContext current);
}
public class TestBean implements IConfigChangedHandler {
static ConfigContext context;
static {
context = new CfgLoader().initConfig( new TestBean());
}
@Override
public void handle(ConfigContext previous, ConfigContext current ) {
context .getCount();
}
public static int getCount() {
return context .getCount();
}
}
public class TestStatic {
public static void main(String[] args) {
TestBean. getCount();
}
}
这个demo在运行的时候,TestBean.getCount会注册handler,回调中又使用了静态方法,引起同一个线程的重入问题。
5. 内部类
内部类的初始化和外部类的初始化没有必然联系,仍然在第一次被使用时,调用内部类的静态块。