深入理解单例七种模式(一)
最近刚看了单例模式,因为之前只在大话上看过就两种简单实现,也把网上介绍的给实践一下,然后对比一下JDK里面有相似的实现,简单讲一下我的理解,欢迎批评哈:
饿汉模式:
1 public class Singleton { 2 3 private static final Singleton instance = new Singleton(); 4 5 private Singleton() { 6 } 7 8 public static Singleton getInstance() { 9 return instance; 10 } 11 12 13 } 14
优点:简单直观,线程安全,在类加载的时候就直接初始化了
缺点:写死的方式不够灵活,有多个classload的时候也会产生多个实例,而且不够lazy loading。
JDK的例子(java.lang.Runtime):
1 public class Runtime { 2 private static Runtime currentRuntime = new Runtime(); 3 4 public static Runtime getRuntime() { 5 return currentRuntime; 6 } 7 8 /** Don't let anyone else instantiate this class */ 9 private Runtime() {}
下面基于类加载机制写了个简单的demo看下这种饿汉模式存在的问题(代码稍做修改):
参考:
http://my.oschina.net/pingpangkuangmo/blog/376328
1 public class Singleton { 2 3 private static final Singleton instance = new Singleton(); 4 5 private Singleton() { 6 System.out.print("类加载:"); 7 System.out.println(this.getClass().getClassLoader()); 8 } 9 10 public static Singleton getInstance() { 11 return instance; 12 } 13 14 }
下面仿照网上的例子写了个简单的类加载器:
1 import java.io.ByteArrayOutputStream; 2 import java.io.FileInputStream; 3 4 public class MyclassLoader extends ClassLoader { 5 6 private String name; 7 private String classPath; 8 9 public MyclassLoader(String name){ 10 super(null); 11 this.name = name; 12 } 13 14 @Override 15 public Class<?> findClass(String name){ 16 byte[] by=getClassBytes(name); 17 return this.defineClass("service.Singleton",by,0,by.length); 18 } 19 20 private byte[] getClassBytes(String name) { 21 byte[] data = null; 22 String classFullPath=name; 23 try { 24 FileInputStream in=new FileInputStream(classFullPath); 25 ByteArrayOutputStream out=new ByteArrayOutputStream(); 26 int i = 0; 27 while ((i = in.read()) != -1) { 28 out.write(i); 29 } 30 data = out.toByteArray(); 31 } catch (Exception e) { 32 e.printStackTrace(); 33 } 34 return data; 35 } 36 37 public String getName() { 38 return name; 39 } 40 41 public void setName(String name) { 42 this.name = name; 43 } 44 45 public String getClassPath() { 46 return classPath; 47 } 48 49 public void setClassPath(String classPath) { 50 this.classPath = classPath; 51 } 52 }
下面是测试程序:
1 public static void main(String[] args) { 2 Singleton singleton_1 = Singleton.getInstance(); 3 Singleton singleton_2 = Singleton.getInstance(); 4 System.out.println("正常类加载单例实例比较:"+"{"+singleton_1+":"+singleton_2+""+"}-->"+singleton_1.equals(singleton_2)); 5 System.out.println("正常加载类信息比较:"+"{"+singleton_1.getClass()+":"+singleton_2.getClass()+""+"}-->"+singleton_1.getClass().equals(singleton_2.getClass())); 6 MyclassLoader myClassLoader1=new MyclassLoader("myClassLoader1"); 7 MyclassLoader myClassLoader2=new MyclassLoader("myClassLoader2"); 8 9 Class singletonClass1 = myClassLoader1.findClass("H:/totalwork/exerse/target/classes/service/Singleton.class"); 10 Class singletonClass2 = myClassLoader2.findClass("H:/totalwork/exerse/target/classes/service/Singleton.class"); 11 12 System.out.println("单独加载器类信息:"+"{"+singletonClass1+":"+singletonClass2+""+"}-->"+singletonClass1.equals(singletonClass2)); 13 14 try { 15 Constructor constructor_1=singleton_1.getClass().getDeclaredConstructor(); 16 Constructor constructor_2=singleton_2.getClass().getDeclaredConstructor(); 17 System.out.println("正常类加载单例构造器比较:"+"{"+constructor_1+":"+constructor_2+""+"}-->"+constructor_1.equals(constructor_2)); 18 19 Constructor constructor1=singletonClass1.getDeclaredConstructor(); 20 Constructor constructor2=singletonClass2.getDeclaredConstructor(); 21 System.out.println("单独加载器构造器比较:"+"{"+constructor1+":"+constructor2+""+"}-->"+constructor1.equals(constructor2)); 22 23 System.out.println("-----------设置权限:start------------"); 24 constructor_1.setAccessible(true); 25 constructor_2.setAccessible(true); 26 System.out.println("-----------设置权限:end--------------"); 27 28 Object obj_1=constructor_1.newInstance(); 29 Object obj_2=constructor_2.newInstance(); 30 System.out.println("正常类加载单例生成实例比较:"+"{"+obj_1.hashCode()+":"+obj_2.hashCode()+""+"}-->"+obj_1.equals(obj_2)); 31 32 System.out.println("-----------设置权限:start------------"); 33 constructor1.setAccessible(true); 34 constructor2.setAccessible(true); 35 System.out.println("-----------设置权限:end--------------"); 36 37 Object obj1=constructor1.newInstance(); 38 Object obj2=constructor2.newInstance(); 39 40 System.out.println("单独加载器单例生成实例比较:"+"{"+obj1+":"+obj2+""+"}-->"+obj1.equals(obj2)); 41 42 } catch (Exception e) { 43 e.printStackTrace(); 44 } 45 46 }
下面是运行结果:
1 类加载:sun.misc.Launcher$AppClassLoader@fb56b1 2 正常类加载单例实例比较:{service.Singleton@290fbc:service.Singleton@290fbc}-->true 3 正常加载类信息比较:{class service.Singleton:class service.Singleton}-->true 4 单独加载器类信息:{class service.Singleton:class service.Singleton}-->false 5 正常类加载单例构造器比较:{private service.Singleton():private service.Singleton()}-->true 6 单独加载器构造器比较:{private service.Singleton():private service.Singleton()}-->false 7 -----------设置权限:start------------ 8 -----------设置权限:end-------------- 9 类加载:sun.misc.Launcher$AppClassLoader@fb56b1 10 类加载:sun.misc.Launcher$AppClassLoader@fb56b1 11 正常类加载单例生成实例比较:{13228332:30472956}-->false 12 -----------设置权限:start------------ 13 -----------设置权限:end-------------- 14 类加载:service.MyclassLoader@1bf3d87 15 类加载:service.MyclassLoader@1bf3d87 16 类加载:service.MyclassLoader@1e4f7c2 17 类加载:service.MyclassLoader@1e4f7c2 18 单独加载器单例生成实例比较:{service.Singleton@10dc6b5:service.Singleton@170bea5}-->false
1、可以看到MyclassLoader是我们自己的类加载器,自定义classloader我们需要通过继承java.lang.ClassLoader来实现,其中private byte[] getClassBytes(String name)函数,就是通过文件流传输讲.class字节码读到byte[] data中。
2、然后交给classloader的defineClass1来实现加载,从源码可以看出来
1 protected final Class<?> defineClass(String name, byte[] b, int off, int len) 2 throws ClassFormatError 3 { 4 return defineClass(name, b, off, len, null); 5 }
...
1 private native Class defineClass1(String name, byte[] b, int off, int len, 2 ProtectionDomain pd, String source);
其中:
1、singleton_1和singleton_2为直接产生的单例对象,我们也可以看到指向同一个对象。也可以看到他们默认为sun.misc.Launcher$AppClassLoader@fb56b1加载器加载。
2、myClassLoader1和myClassLoader2是我们构造的两个类加载器。
3、singletonClass1和singletonClass2分别是两个构造器构造的Class对象。其实对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机的唯一性,每一个类加载器,都拥有一个独立的类命名空间。通俗表达:比较两个类是否“相等”,只有在这两个类由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类必定不相等(摘自深入理解Java虚拟机)。
通过上面的解释,下面几个对象比较的结果其实就可以理解了。
4、constructor_1和constructor_2为单例对象class得到的,constructor1和constructor2为加载类得到的。
其实有两种方法可以执行类的构造函数:
Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数, 不过要求构造函数为public,上面的单例会直接抛出java.lang.IllegalAccessException异常。
Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。
5、从结果来看constructor_1和constructor_2是equal的,constructor1和constructor2压根没有可比性,所以肯定为false。
先写这么多,回头再补充吧。
这里还有几个疑问没解决,知道的希望帮我解答一下:
1、constructor_1和constructor_2分别new出来的实例obj_1和obj_2为什么不相等?我查看了一下它们俩的hashCode,确实不一样,但是它们两个是同一个系统类加载器加载的,有点搞不懂。
2、Object obj1=constructor1.newInstance();这个语句执行了两次无参构造函数,我单步发现obj1生成的过程中,先执行了无参构造函数再执行的静态成员初始化语句
private static final Singleton instance = new Singleton();谁能告诉我原因呢?
感谢网友回答:对于第一个问题,这两个对象本应该就是不一样的,一旦能用反射,就没有单例可言了,使用枚举可以阻止反射。
对于第二个问题,就是在加载类的时候就会去执行static方法,执行的时候发现是new 本身,就去执行构造函数了。类加载完成之后,紧接着执行constructor3.newinstance方法,又会执行一次构造方法,所以会执行两次。所以对于系统默认类加载器只是把静态成员初始化了而已,没有再像newinstance这样调用构造函数。
浙公网安备 33010602011771号