我们写在static块中的代码,对static变量进行的赋值,最终都会在类的初始化中进行,本来这里是没有必要写文章的
但是Java为了保证初始化工作只进行一次,对多线程下的初始化方法《clinit()》(这里应该使用尖括号的,但是csdn不知道为什么,用尖括号会出现错误,因此使用书名号代替)
方法进行了同步和加锁,所以如果在静态块中进行费时操作,会导致其他线程被阻塞式等待
而这一切都是隐性的,很难排查,所以还是有必要写一篇文章的
将一段包含静态块和静态变量赋值的类Javap反编译
源代码
public class Test{
static {
System.out.println("test");
}
static String s="test";
public static void main(String[] args) {
}
}
反编译后(节选clinit方法)
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String test
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: ldc #3 // String test
10: putstatic #5 // Field s:Ljava/lang/String;
13: return
LineNumberTable:
line 3: 0
line 5: 8
可以看出,我们打印字符串的操作,还有给静态变量赋值的操作,均被放在了clinit方法中
下面我们在static块中执行一些耗时的操作
public class Test implements Runnable{
static {
if(true) {
System.out.println(Thread.currentThread().getName()+"进入static块中");
for (; ; ) {
}
}
}
public static void main(String[] args) {
new Thread(new Test(),"1").start();
new Thread(new Test(),"2").start();
new Thread(new Test(),"3").start();
new Thread(new Test(),"4").start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行线程");
}
}
由于jvm虚拟机会自动加载main方法所在的类,main线程进入了static,而1到4线程全部被阻塞住,等待static完成
所以尽量不要在多线程情况下在static中进行过于复杂的操作
下面是clinit()的一些补充说明
- clinit()并不是必须的,如果没有静态块或者对静态变量赋值,clinit()是可以不生成的
- 接口中同样可能生成clinit()方法,因为接口中虽然不能使用static块,但是可以进行赋值