单例模式

单例模式

1、概述

核心作用:

保证一个类只有一个实例,并且提供一个访问该实例的全局访问点

常见场景:

  • Window的任务管理器
  • Window的回收站
  • 项目中,读取配置文件的类,一般也只有一个对象,没必要每次都去new对象读取
  • 网站的计数器一般也会采用单例模式,可以保证同步
  • 数据库连接池的设计一般也是单例模式
  • 在Servlet编程中,每个Servlet也是单例的
  • 在Spring中,每个Bean默认就是单例的

优点:

  • 由于单例模式只生成一个实例,减少了系统性能开销
  • 单例模式可以在系统设置全局访问点,优化共享资源访问

常见的五种单例模式实现方式:

  • 饿汉式(线程安全,调用效率高,不能延时加载)
  • 懒汉式(线程安全,调用效率不高,可以延时加载)
  • DCL懒汉式(由于JVM底层内部模型原因,偶尔会出现问题,不建议使用)
  • 饿汉式改进、静态内部类式(线程安全,调用效率高,可以延时加载)
  • 枚举单例(线程安全,调用效率高,不能延时加载)

2、饿汉式

饿汉式代码:

package pers.mobian.singleton;

//饿汉式单例
public class SingletonTest01 {
    //1.私有化构造器
    private SingletonTest01() {
    }

    //2.类初始化的时候,立即加载该类的对象
    private static SingletonTest01 instance = new SingletonTest01();

    //3.提供获取该对象的方法,此处没有synchronized,效率高
    public static SingletonTest01 getInstance() {
        return instance;
    }
}

class Test {
    public static void main(String[] args) {
        SingletonTest01 instance1 = SingletonTest01.getInstance();
        SingletonTest01 instance2 = SingletonTest01.getInstance();
        System.out.println(instance1 == instance2);
    }
}

测试结果:

true

总结:

此种单例模式十分简单,但是如果在SingletonTest01类中,存在初始化的数据,每次仅仅初始化类,并未真真调用该类时,就会出现占用空间的现象。代码如下:

public class SingletonTest01 {
    byte[] a1 =  new byte[1024];
    byte[] a2 =  new byte[1024];
    byte[] a3 =  new byte[1024];
    private SingletonTest01() {
    }
    private static SingletonTest01 instance = new SingletonTest01();
    public static SingletonTest01 getInstance() {
        return instance;
    }
}

于是出现了懒汉式单例模式。


3、懒汉式

懒汉式代码:

package pers.mobian.singleton;

public class SingletonTest02 {
    byte[] a1 = new byte[1024];
    byte[] a2 = new byte[1024];
    byte[] a3 = new byte[1024];

    //1.私有化构造器
    private SingletonTest02() {
    }

    //2.类初始化的时候,立即加载该类的对象
    private static SingletonTest02 instance;

    //3.提供获取该对象的方法,此处有synchronized,效率较低
    public static synchronized SingletonTest02 getInstance() {
        if (instance == null) {
            instance = new SingletonTest02();
        }
        return instance;
    }

}

class Test1 {
    public static void main(String[] args) {
        SingletonTest02 instance1 = SingletonTest02.getInstance();
        SingletonTest02 instance2 = SingletonTest02.getInstance();
        System.out.println(instance1 == instance2);
    }
}

测试结果:

true

总结:

此方法可以延时加载,仅仅调用该方法后,才会初始方法区中的数据,继而避免出现饿汉式单例模式的问题。但是使用了synchronized关键字,在达到了线程安全的同时会出现效率低下的问题,继而引入了DCL(双重检测锁)懒汉式。


4、DCL(Double Check Lock)懒汉式

DCL懒汉式代码:

package pers.mobian.singleton;

public class SingletonTest03 {
    //1.私有化构造器
    private SingletonTest03() {
    }

    //2.类初始化的时候,立即加载该类的对象
    //此处还需要加上volatile关键字:此关键字直接指向内存,防止在运行第三步的时候,出现多个线程运行到synchronized代码块后,if判断语句之前
    private volatile static SingletonTest03 instance;

    //因为此操作不是原子性操作,所以在内存中会出现这样三步的操作
    /*
    * 1.分配内存
    * 2.执行构造方法
    * 3.指向地址
    * */
    //这也就是为什么要使用volatile关键字
    //3.提供获取该对象的方法,此处没有synchronized,效率较低
    public static SingletonTest03 getInstance() {
        //此处为第一重锁,判断对象是否被创建
        if (instance == null) {
            synchronized (SingletonTest03.class) {
                //此为第二重锁,判断自己是否是第一个拿到锁的,继而创建对象
                if (instance == null) {
                    instance = new SingletonTest03();
                }
            }
        }
        return instance;
    }
}

class Test3 {
    public static void main(String[] args) {
        SingletonTest03 instance1 = SingletonTest03.getInstance();
        SingletonTest03 instance2 = SingletonTest03.getInstance();
        System.out.println(instance1 == instance2);
    }
}

测试结果:

true

总结:

使用此单例模式,可以避免出现懒汉式单例synchronized关键字将整个方法锁住,继而影响效率。DCL单例中的synchronized关键字只锁了if判断成功以后的代码块,如果instance不是null,则会跳过直接return,不会降低效率。


5、饿汉式改进、静态内部类式

饿汉式改进、静态内部类式代码:

package pers.mobian.singleton;
//静态内部类实现
public class SingletonTest04 {
    private SingletonTest04(){}
    private static class InnerClass{
        private static final SingletonTest04 instance = new SingletonTest04();
    }
    public static SingletonTest04 getInstance(){
        return InnerClass.instance;
    }
}

class Test4{
    public static void main(String[] args) {
        SingletonTest04 instance1 = SingletonTest04.getInstance();
        SingletonTest04 instance2 = SingletonTest04.getInstance();
        System.out.println(instance1 == instance2);
    }
}

测试结果:

true

总结:

采用此方法,可以延时加载,并且线程安全。但是java中存在反射机制,能够改变内部的private关键字,于是引入了枚举单例模式

反射破坏单例模式测试代码:

package pers.mobian.singleton;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

//静态内部类实现
public class SingletonTest04 {
    private SingletonTest04(){}
    private static class InnerClass{
        private static final SingletonTest04 instance = new SingletonTest04();
    }
    public static SingletonTest04 getInstance(){
        return InnerClass.instance;
    }
}

class Test4{
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        //利用反射获取其对应的构造器方法
        Constructor<SingletonTest04> declaredConstructor = SingletonTest04.class.getDeclaredConstructor(null);
        //开启访问权限
        declaredConstructor.setAccessible(true);
        //直接调用方法,进行实例化输出
        SingletonTest04 instance1 = SingletonTest04.getInstance();
        SingletonTest04 instance2 = declaredConstructor.newInstance();
        System.out.println(instance1 == instance2);
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

执行结果:

false
2137211482
920011586

6、枚举单例

newInstance类源码:

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, clazz, modifiers);
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            //此行代码表示,不能反射枚举类型的对象
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

枚举单例测试代码:

package pers.mobian.singleton;

public enum SingletonTest05 {
    INSTANCE;
    public SingletonTest05 getInstance(){
        return INSTANCE;
    }
}
class test05{
    public static void main(String[] args) {
        SingletonTest05 instance1 = SingletonTest05.INSTANCE;
        SingletonTest05 instance2 = SingletonTest05.INSTANCE;
        System.out.println(instance1==instance2);
    }
}

测试结果:

true

总结:

利用枚举的方式创建单例模式,可以避免反射对其内部进行关键字的破坏,继而导致单例模式失效。它的缺点也是不能延时加载


7、总结

不同的单例模式拥有不同的运用场景,灵活运用。将单例模式的思想带入到我们的编码中,才是学习设计模式的关键。

你可能感兴趣的:(设计模式)