零度冰川

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

深入理解单例七种模式(一)

最近刚看了单例模式,因为之前只在大话上看过就两种简单实现,也把网上介绍的给实践一下,然后对比一下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这样调用构造函数。

 

posted on 2013-12-16 15:07  零度冰川  阅读(537)  评论(0)    收藏  举报