单例模式

单例模式:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
优点:
1、省略创建对象所花费的时间减少系统开销,尤其是重量级对象。
2、减少对象的创建,减轻GC压力。
3、设置全局访问入口,优化资源访问。
 
 
一、最简单的实现
方案1:
 1 public class SingletonClass {
 2     private static final SingletonClass instance = new SingletonClass();
 3     
 4     private SingletonClass(){}
 5     
 6     public static SingletonClass getInstance(){
 7         return instance;
 8     }
 9     
10 }
View Code
SingletonClass对象声明为final类型结合Java的类加载机制,保证了对象的唯一性。
 
二、延时加载
方案1乍看上去简单可行,但会出现两个问题:
问题1:可以通过反射再创建实例。
问题2:可以通过反序列化创建实例。
以上两个问题有点偏,先掩耳盗铃不考虑。
问题3:对象在类加载时就创建了不管使用与否,创建对象消耗小还好,消耗大的话就容易造成浪费。
对于问题2的解决方案是延时加载。
方案2:
 1 public class SingletonClass {
 2     private static SingletonClass instance = null;
 3     
 4     private SingletonClass(){}
 5     
 6     public static SingletonClass getInstance(){
 7         if(null == instance){
 8             instance = new SingletonClass();
 9         }
10         
11         return instance;
12     }
13     
14 }
View Code
不在类加载时创建,等到需要使用的时候在进行创建。
 
三、线程安全
方案2 解决了问题3,但这样做又会引出新的问题4:同步。假如A、B 两个线程同时调用getInstance(),A 进行null == instance判断通过,然后instance对象进行创建且没有创建完或者 A 让出CPU时间片。在此时 B 执行到nulll == instance这里,也能通过判断。结果就是 A、B 拿到的是不同的对象。针对这种情况,加锁就好。
方案3:
 1 public class SingletonClass {
 2     private static SingletonClass instance = null;
 3     
 4     private SingletonClass(){}
 5     
 6     public static SingletonClass getInstance(){
 7         synchronized (SingletonClass.class) {
 8             if(null == instance){
 9                 instance = new SingletonClass();
10             }
11         }
12         
13         return instance;
14     }
15     
16 }
View Code
在每次 null==instance判断时加锁,保证同步。
 
四、性能优化
方案3解决了同步问题,但是每次调用getInstance()都加一次锁,性能有点低哦。那能不能在同步代码块外面再加一个非空判断呢?
方案4:
 1 public class SingletonClass {
 2     private static SingletonClass instance = null;
 3     
 4     private SingletonClass(){}
 5     
 6     public static SingletonClass getInstance(){
 7         if(null == instance){
 8             synchronized (SingletonClass.class) {
 9                 System.out.println("进入锁");
10                 if(null == instance){
11                     instance = new SingletonClass();
12                 }
13             }
14         }        
15         return instance;
16     }
17     
18     public static void main(String[] args) {
19         for (int i = 0; i < 10000; i++) {
20             SingletonClass.getInstance();
21         }
22         System.out.println("处理完成");
23     }
24     
25 }
View Code
运行得到的结果:
编译后的代码:
编译后的代码与代码逻辑一致,做两层非空判断,线程只在第一次进入了锁,后面都是直接返回instance对象。
 
方案4可行,但是总感觉怪怪的,还有没有其他写法呢?
答案是有,使用内部类,在调用时加载它。
方案5:
 1 public class SingletonClass {
 2     
 3     private SingletonClass(){}
 4     
 5     private static class innerSingletonClass{
 6         private static final SingletonClass instance = new SingletonClass();
 7     }
 8     
 9     public static SingletonClass getInstance(){
10         return innerSingletonClass.instance;
11     }
12         
13 }
View Code

 

五、反序列化
上面方案4和方案5 已经能应付绝大多数的单例需求了,但是它们都是在没有考虑问题1、问题2 这两种比较极端的情况。先来看反序列化(序列化的相关知识自行搜索)。
方案6:
 1 public class SingletonClass implements Serializable{
 2     private static final long serialVersionUID = 1L;
 3     
 4     private static SingletonClass instance = null;
 5     
 6     private SingletonClass(){}
 7     
 8     public static SingletonClass getInstance(){
 9         if(null == instance){
10             synchronized (SingletonClass.class) {
11                 if(null == instance){
12                     instance = new SingletonClass();
13                 }
14             }
15         }        
16         return instance;
17     }
18     
19     private Object readResolve(){//阻止生成新的实例,总是返回当前对象
20         return instance;
21     }
22     
23 }
View Code
readResolve():如果类中定义了这个特殊序列化方法,在这个对象被序列化之后就会调用它。它必须返回一个对象,而该对象之后会成为  readObject 的返回值。
 
六、反射
通过反射调用构造方法来创建对象,要在代码层面防范这种行为,现阶段我还不知道,但是可以通过枚举来防止反射破坏单例。
方案7:
 1 public enum SeasonEnum {
 2     /**
 3      * 在加载枚举类时初始化。
 4      * 线程安全,与java的类加载机制相关。
 5      * 全局唯一,反射不能更改。
 6      * 反序列化后还是同一个对象
 7      * */
 8     Spring(1);
 9     
10     private final int value;//可以像普通类一样定义Field,但是要定义在 枚举 后面,不然编译器会报错。
11     
12     private SeasonEnum(int v) {//编译器只允许器定义成私有的。
13         value = v;
14     }
15 //    private SeasonEnum() {//报错,不能重写无参构造方法
16 //        
17 //    }
18     
19     public int getValue() {
20         return value;
21     }
22     
23     public static void main(String[] args) throws Exception {        
24         Class<SeasonEnum> cla = SeasonEnum.class;
25         Constructor<?>[] colls = cla.getDeclaredConstructors();//获得所有的构造器,拿到的是无参的创建枚举的构造方法。
26         System.out.println("获得的构造方法数量:" + colls.length);//获得的构造方法数量:1
27         for (Constructor<?> constructor : colls) {
28             System.out.println("构造方法名称:" + constructor.getName());//构造方法名称:testEnum.SeasonEnum
29             /**
30              * 运行报错
31              * Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects 
32              * at java.lang.reflect.Constructor.newInstance(Constructor.java:402)
33              * */
34             constructor.newInstance(0);            
35             System.out.println(SeasonEnum.Spring);
36         }
37         
38     }
39 }
View Code
枚举算是 现阶段单例的终极解决方案,简单线程安全,防反射,防反序列化。
 
推荐在代码中使用枚举来实现单例!
 
参考:
《Effective Java》【Joshua Bloch 】
《Java 程序性能优化》【葛一鸣等】



posted @ 2017-05-04 17:32  chenzl1024  阅读(106)  评论(0编辑  收藏  举报