java static块使用

 

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. 内部类

内部类的初始化和外部类的初始化没有必然联系,仍然在第一次被使用时,调用内部类的静态块。

转载于:https://my.oschina.net/u/1267704/blog/704916

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值