JAVA的23种设计模式---单例模式

来源:互联网 发布:淘宝店多少单一颗心 编辑:程序博客网 时间:2024/05/16 14:34

概要:

该文章参考了《设计模式之禅》一书及大牛Lena-Yang的博客文章

1.该文章阐述了单例模式的基础及进阶代码表现方式;
2.该文章适合初学设计模式的技术人员研习;
3.该文章有许多不足之处,请各位大咖指正,喷子绕道;

正文:

单例模式:确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

基础的单例模式代码实现:

1.饿汉式单例:

public class Singleton {        //饿汉式单例,实例化过程也可以写在静态代码块中    private static final Singleton SINGLETON = new Singleton();     //私有构造器,限制产生多个对象    private Singleton(){            }       //获取实例对象    public static Singleton getSingleton(){             return SINGLETON;           }       //类中其他方法,尽量是static    public static void doSomething(){           }}

2.懒汉式单例:

public class Singleton {    //懒汉式单例,不推荐,仅用于基础原理展示    private static Singleton singleton = null;    //私有构造器,限制产生多个对象    private Singleton(){        }    //获取实例对象,不安全,需加锁    public static synchronized Singleton getSingleton(){        if(singleton == null){            singleton = new Singleton();        }        return singleton;    }}

进阶的单例模式代码实现:

1.基于懒汉式的双重检查实现:

public class Singleton {    //双重检查单例    private static volatile Singleton singleton = null;    //私有构造器,限制产生多个对象    private Singleton(){        }    //双重检查获取实例对象    public static Singleton getSingleton(){        if (singleton == null) {                synchronized (Singleton.class) {                    if (singleton == null) {                        singleton = new Singleton();                    }                }            }        return singleton;    }}

注:
a:关键字volatile(不稳定的):把该变量声明为volatile,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取,为了防止多线程环境下的不安全情况的发生。一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰。
b:Double-Check locking概念
c:Happens-Before概念:在每一个构造函数结束的地方都有一个freeze动作,在构造函数返回前,所有的final成员变量都要完成初始化。使用volatile来修饰static的singleton也可以保证成功,然而一些jvm并没有正确实现volatile语义,所以这种方法并不是那么保险。

2.基于饿汉式的静态内部类实现:

public class Singleton {    //私有构造器,限制产生多个对象    private Singleton(){    }    //静态内部类产生实例对象,JVM帮助我们保证了线程的安全性    private static class SingletonInstance {        private static final Singleton INSTANCE = new Singleton();    }    //获取实例对象,利用了JVM类加载时的特性,第一次使用SingletonInstance时才进行初始化    public static Singleton getInstance() {        return SingletonInstance.INSTANCE;    }}

注:
a:Lazy initialization holder class模式:综合使用了Java的类级内部类和多线程缺省同步锁的知识,很巧妙的同时实现了延迟加载和线程安全。
b:以上方式含有反射及反序列化漏洞,简单来讲通过这两种方式获取对象会破坏单例模式,产生不同的实例对象。

解决方式:

单例类:

import java.io.ObjectStreamException;import java.io.Serializable;/** * 静态内部类实现方式为例 * 线程安全,调用效率高,并且实现了延迟加载  * 解决反射和反序列化漏洞 * @author Administrator * */public class Singleton implements Serializable{    //虽然在这个例子中没用,但是养成习惯    private static final long serialVersionUID = 1L;        //私有构造器,限制产生多个对象    private Singleton(){        //防止反射获取多个对象的漏洞        if (null != SingletonInstance.INSTANCE)              throw new RuntimeException();     }    //静态内部类产生实例对象,JVM帮助我们保证了线程的安全性    private static class SingletonInstance {        private static final Singleton INSTANCE = new Singleton();    }    //获取实例对象,利用了JVM类加载时的特性,第一次使用SingletonInstance时才进行初始化,调用效率高    public static Singleton getInstance() {        return SingletonInstance.INSTANCE;    }    //防止反序列化获取多个对象的漏洞      private Object readResolve() throws ObjectStreamException {            return SingletonInstance.INSTANCE;      } }

测试类:

import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;/** * 获取实例对象测试类 * @author Administrator * */public class Attack {    public static void main(String[] args) {        Singleton sc1 = Singleton.getInstance();          Singleton sc2 = Singleton.getInstance();          //sc1,sc2是同一个对象          System.out.println(sc1);        System.out.println(sc2);          //通过反射的方式直接调用私有构造器(通过在构造器里抛出异常可以解决此漏洞)        try {            Class<Singleton> clazz = (Class<Singleton>) Class.forName(Singleton.class.getName());              Constructor<Singleton> c = clazz.getDeclaredConstructor(null);              c.setAccessible(true); // 跳过权限检查              Singleton sc3 = c.newInstance();              Singleton sc4 = c.newInstance();             // sc3,sc4不是同一个对象              System.out.println("通过反射的方式获取的对象sc3:" + sc3);            System.out.println("通过反射的方式获取的对象sc4:" + sc4);         } catch (Exception e) {            System.err.println("单例模式被破坏");        }        //通过反序列化的方式构造多个对象(类需要实现Serializable接口)          try {            //1.把对象sc1写入硬盘文件              FileOutputStream fos = new FileOutputStream("object.out");              ObjectOutputStream oos = new ObjectOutputStream(fos);              oos.writeObject(sc1);              oos.close();              fos.close();                          //2.把硬盘文件上的对象读出来              ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.out"));              //如果对象定义了readResolve()方法,readObject()会调用readResolve()方法。从而解决反序列化的漏洞              Singleton sc5 = (Singleton) ois.readObject();              //反序列化出来的对象,和原对象,不是同一个对象。如果对象定义了readResolve()方法,可以解决此问题。              System.out.println("对象定义了readResolve()方法,通过反序列化得到的对象:" + sc5);               ois.close();         } catch (Exception e) {            // TODO: handle exception        }          }}

3.枚举实现:

public enum Singleton {    SINGLETON;    public void whateverMethod() {            }}

注:JDK1.5后可以使用