JAVA设计模式之单例模式

来源:互联网 发布:淘宝怎么更改支付宝 编辑:程序博客网 时间:2024/05/21 04:22

前言:
本文旨在介绍常见的单例模式实现方式,以及如何破解单例模式和防止破解,最后对比几种模式的调用效率。
一、常见的单例模式种类
1、饿汉模式
其一:在Java底层类加载机制中,static变量会在类加载时初始化,此时不会涉及多个线程对象访问该对象;
JVM保证只会装载一次该类,即不会发生并发访问的问题,因此不需要synchronized关键字。但是如果在写业务逻辑
时只是加载本类,而不是调用获取对象的方法,这样的模式会造成资源浪费。
其二:因为在类初始化时就加载了该类对象,即不存在延时加载,但是平常工作中可能更多的需要延时加载;
其三:调用效率高。

/** * @desc 饿汉模式 * @author 木子Lee * @time 2017年9月21日21:28:43 * @see jdk1.8 */public class Singleton01 {   private static Singleton01 instance = new Singleton01();   private Singleton01(){}  public static  Singleton01 getInstance(){    return instance;   }}

2、懒汉模式
其一:延时加载,即真正使用的时候才加载;
其二:资源利用率高,但是每次调用getInstance()方法都需要同步,并发效率低;
其三:因其方法需要同步,调用效率低。

/** * @desc 懒汉模式 * @author 木子Lee * @time 2017年9月21日22:01:27 * @see jdk1.8 */public class Singleton02 {   private static Singleton02 instance;   private Singleton02(){}   public static synchronized Singleton02 getInstance(){      if(instance==null){    instance=new Singleton02();      }    return instance;   }}

3、 双重检测锁机制实现
其一:将内容同步到if内部,提高执行效率,不需要每次都同步获取对象,即只有第一次才同步;
其二:编译器优化以及Java虚拟机JVM底层内部模型暂时不能完美的支持此模式,不建议使用。

/** * @desc 双重检测锁机制模式 * @author 木子Lee * @time 2017年9月21日22:10:45 * @see jdk1.8 */public class Singleton03 {   private static Singleton03 instance=null;   private Singleton03(){}   public static Singleton03 getInstance(){    if(instance==null){      Singleton03  s;    synchronized(Singleton03.class){      s=instance;      if(s==null){        synchronized (Singleton03.class) {        if(s==null){           s=new Singleton03();          }           }        instance=s;       }       }         }    return instance;   }}

4、静态内部类模式
其一:因外部类没有static属性,则不会像饿汉模式那样立即加载对象,即是一种懒加载方式;
其二:只有调用真正的getInstance()才会加载静态内部类。由于JAVA类加载机制在加载内部类时是线程安全的。
其三:instance是static final类型,保证了JVM内存中只有一个实例的存在,而且只能被赋值一次,即保证了线程安全
其四:兼备了并发高效和延时加载的优势,建议使用。

/** * @desc 静态内部类模式 * @author 木子Lee * @time 2017年9月21日22:22:15 * @see jdk1.8 */public class Singleton04 {   private Singleton04(){}   private static class Singleton04Classinstance{      private static final Singleton04 instance=new Singleton04();   }    public static Singleton04 getInstance(){    return Singleton04Classinstance.instance;    }}

5、枚举模式
其一:Java中枚举本身就是单例;
其二:由于JVM提供保障,从而避免了通过反射和反序列化漏洞;
其三:没有延时加载。

/** * @desc 枚举模式 * @author 木子Lee * @time 2017年9月21日22:31:29 * @see jdk1.8 */public enum Singleton05 {    //定义枚举元素,即代表单例实例    INSTANCE;    public void singletonOperation(){       //单例自己的操作,即功能处理   }}

二、反射、序列化和反序列化如何破解单例(除枚举模式)
1、反射破解懒汉式

/*** 通过Java反射破解懒汉式单例,一般情况下只是需要写项目不考虑防止破解;但是写产品时需要考虑这点 * @param args* @throws ClassNotFoundException* @throws NoSuchMethodException* @throws SecurityException* @throws InstantiationException* @throws IllegalAccessException* @throws IllegalArgumentException* @throws InvocationTargetException */public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException,          InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {   Singleton02 S1=Singleton02.getInstance();   Singleton02 S2=Singleton02.getInstance();   System.out.println(S1);   System.out.println(S2);   Class<Singleton02> clazz=(Class<Singleton02>)Class.forName("test.Singleton02");   Constructor<Singleton02> c=clazz.getDeclaredConstructor(null);   c.setAccessible(true);   Singleton02 s3 =c.newInstance();   Singleton02 s4 =c.newInstance();   System.out.println(s3);   System.out.println(s4); }

注:运行结果如下

S1:test.Singleton02@15db9742S2:test.Singleton02@15db9742S3:test.Singleton02@6d06d69cS4:test.Singleton02@7852e922

可以看出S1和S2是同一个对象实例,但是S3和S4不再是同一个对象实例了。
2、如何防止反射破解
可以在实现单例类的私有化构造器中抛出运行时异常等操作,即在懒汉式中对其构造器做如下修改:

private Singleton02(){   if(instance!=null){    throw new RuntimeException();  } }

打印抛出异常:

test.Singleton02@15db9742test.Singleton02@15db9742Exception in thread "main" java.lang.reflect.InvocationTargetException    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)    at java.lang.reflect.Constructor.newInstance(Unknown Source)    at Singleton05.main(Singleton05.java:43)Caused by: java.lang.RuntimeException    at test.Singleton02.<init>(Singleton02.java:15)    ... 5 more

建议:一般写项目的情况下不需要考虑防止类似破解,但是写诸如Spring等产品时需要考虑类似破解。
3、序列化和反序列化破解
其一:对于防止的单例类需要实现序列化接口
测试代码:

/*** 以懒汉式为例通过序列化和反序列化破解单例* @param args* @throws IOException * @throws ClassNotFoundException */public static void main(String[] args) throws IOException, ClassNotFoundException {   Singleton02 s1=Singleton02.getInstance();   Singleton02 s2=Singleton02.getInstance();   System.out.println(s1);   System.out.println(s2);   FileOutputStream out=new FileOutputStream("e:/a.txt");   ObjectOutputStream oos= new ObjectOutputStream(out);   oos.writeObject(s1);   //仅仅做测试,就不做严密的写法   oos.close();   out.close();   ObjectInputStream in=new ObjectInputStream(new FileInputStream("e:/a.txt"));   Singleton02 s3=(Singleton02)in.readObject();  System.out.println(s3);}

运行结果如下:

S1:test.Singleton02@15db9742S2:test.Singleton02@15db9742S3:test.Singleton02@4c873330

4、防止序列化和反序列化破解
需要在单例类中加入如下方法:

private Object readResolve() throws ObjectStreamException{    return instance;}

注:加入该方法后,反序列化时直接返回指定的该对象实例,不再创建新的对象实例,从而防止序列化和反序列化破解
防止反射和序列化破解的完整单例类方法代码:

/** * @desc 懒汉模式 * @author 木子Lee * @time 2017年9月21日22:01:27 * @see jdk1.8 */public class Singleton02 implements Serializable{    private static Singleton02 instance;    private Singleton02(){    if(instance!=null){        throw new RuntimeException();    }    }   public static synchronized Singleton02 getInstance(){    if(instance==null){      instance=new Singleton02();    }      return instance;  }   private Object readResolve() throws ObjectStreamException{    return instance;  }}

5、测试几种单例模式的调用效率

/*** 测试多线程环境下各种模式耗时* @param args* @throws Exception */public static void main(String[] args) throws Exception{    long startTime=System.currentTimeMillis();    System.out.println(startTime);    int threadCount=10;    final CountDownLatch countDownLatch=new CountDownLatch(threadCount);    for(int i=0;i<threadCount;i++){    new Thread(new Runnable() {     @Override     public void run() {     for(int i=0;i<100000;i++){       Object o=Singleton01.getInstance();     }    countDownLatch.countDown();                }     }).start();  }   countDownLatch.await();   long endTime=System.currentTimeMillis();   System.out.println("总耗时:"+(endTime-startTime));}

说明:本次测试仅仅在本次特定的环境下,不同环境得出的数值可能不同

各种模式运行结果:
饿汉式:12
懒汉式:57
静态内部类:16
枚举类:10
双重检测锁:8