设计模式之单例模式

思维导图:java学习思维导图| ProcessOn免费在线作图,在线流程图,在线思维导图   

gittee地址:zsc-design: 设计模式 - Gitee.com 

定义:一个类在任何一种情况下都绝对只有一个实例,并提供一个全局访问点

1.饿汉式单例:在启动时就加载

2.懒汉式单例:在使用的时候在进行初始化

3.注册式单例:将每一个实例都缓存到统一的容器中,使用唯一的标识获取实例

4.ThreadLocal单例:天生线程安全,保证线程内部得全局唯一

饿汉式单例

写法1:

public class HungrySingleton1 {

    private final static HungrySingleton1 INSTANCE = new HungrySingleton1();

    private HungrySingleton1(){

    }

    private static HungrySingleton1 getInstance(){
        return INSTANCE;
    }
}

写法2:

public class HungerSingleton2 {

    private final static HungerSingleton2 INSTANCE;

    static {
        INSTANCE = new HungerSingleton2();
    }

    private HungerSingleton2(){

    }

    private static HungerSingleton2 getInstance(){
        return INSTANCE;
    }
}

优点: 启动时就加载,效率高

缺点:在某些情况下,如果该实例不被使用到那么就会造成内存的浪费

思考:如何避免内存的浪费?在使用时再进行初始化,因此就有了懒汉式单例

懒汉式单例

懒汉式单例写法1

public class LazySingleton1 {

    private static LazySingleton1 INSTANCE;

    private LazySingleton1(){

    }

    private static LazySingleton1 getInstance(){
        if (INSTANCE == null) {
            return new LazySingleton1();
        }
        return INSTANCE;
    }
}

优点: 在使用的时候调用getInstance方法才初始化实例对象,避免了内存浪费

缺点:线程不安全,当两个线程同时进入到if判断条件时,就会创建两个不同的对象,违背了单例模式的定义

思考:如何避免线程不安全问题? 在方法上增加synchronized关键字,这样就可以只有一个线程进入了

public class LazySingleton2 {

    private static LazySingleton2 INSTANCE;

    private LazySingleton2(){

    }

    private synchronized static LazySingleton2 getInstance(){
        if (INSTANCE == null) {
            return new LazySingleton2();
        }
        return INSTANCE;
    }
}

优点:线程安全了

缺点:但容易影响系统性能,全阻塞在了方法的外面。举个非常形象的例子:在地铁站你不能在把人们都堵在地铁外面,用户体验肯定不好,而应该把人放到地铁里面等。

思考: 如何提高系统性能?把锁加在方法里面,进行双重检查

public class LazySingleton3 {
    private static LazySingleton3 INSTANCE;
    private LazySingleton3(){

    }
    private static LazySingleton3 getInstance(){
        if (INSTANCE == null) {
            synchronized (LazySingleton3.class) {
                if (INSTANCE == null) {
                    return new LazySingleton3();
                }
            }

        }
        return INSTANCE;
    }
}

优点: 线程安全了,性能提上去了

缺点: 但是还有一个指令重排序的问题 在定义属性时增加volatile关键字(后续博客详解);但是这种写法可读性差,不够优雅

采用静态内部类实现单例

public class LazySingleton4 {

    private LazySingleton4(){

    }

    private static LazySingleton4 getInstance(){
        return LazyInner.INSTANCE;
    }

    private static class LazyInner{
        private final static LazySingleton4 INSTANCE = new LazySingleton4();
    }
}

优点:写法优雅了

缺点:但是还会有反射可以破坏单例。如下

反射破坏单例


        Class clazz = LazySingleton4.class;
        try {
            Constructor c = clazz.getDeclaredConstructor(null);
            c.setAccessible(true); //设置访问权限
            Object instance1 = c.newInstance();
            Object instance2 = c.newInstance();
            System.out.println(instance1);  // com.zsc.singleton.lazysingleton.LazySingleton4@1540e19d
            System.out.println(instance2);  // com.zsc.singleton.lazysingleton.LazySingleton4@677327b6
            System.out.println(instance1 == instance2); //false
        } catch (Exception e) {
            e.printStackTrace();
        }

通过反射可以获得两个不同的对象

思考:如何解决反射破坏单例?反射主要是通过构造器访问到我们创建实例的方法的,因此在构造器里加判断方法就可以解决了。如下:

public class LazySingleton4 {

    private LazySingleton4() throws Exception {
        if (LazyInner.INSTANCE != null) {
            throw new Exception("不允许非法访问!");
        }
    }

    private static LazySingleton4 getInstance(){
        return LazyInner.INSTANCE;
    }

    private static class LazyInner{
        private final static LazySingleton4 INSTANCE;

        static {
            try {
                INSTANCE = new LazySingleton4();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

优点: 反射破环不了单例了

缺点: 写法不够简洁,优雅

注册式单例

枚举式单例(注册式单例的一种)

public enum EnumSingleton {

    INSTANCE;

    private Object data;


    private static EnumSingleton getInstance(){
        return INSTANCE;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

优点:线程安全,不被反射破坏

缺点: 在创建的时候就放入了map,在某种情况下存在资源浪费

思考:为什么不会被反射破坏?为什么是线程安全的?如何解决资源浪费的问题,ioc容器单例模式的创建应运而生

枚举式单例不被反射破坏的源码(与上述懒汉式单例解决的方法一致,但是人家是官方的,咱们自己的就不优雅,手动狗头)

设计模式之单例模式_第1张图片

为什么是线程安全的,这是跟enum的结构有关,它是一个map形式的存储方式,在我们sheng'ming

设计模式之单例模式_第2张图片

设计模式之单例模式_第3张图片

IOC容器单例模式(借鉴了枚举类单例的思想)

public class IocSingleton {

    private IocSingleton(){
    }

    private static Map ioc = new ConcurrentHashMap();

    public static Object getInstance(String className) {
        Object instance = null;
        if (!ioc.containsKey(className)) {
            try {
                instance = Class.forName(className).newInstance();
                ioc.put(className, instance);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return instance;
        }
        return instance;
    }
}

缺点:存在线程安全问题,具体解决方法可参考spring源码

ThreadLocal单例模式

public class ThreadLocalSingleton {

    private static final ThreadLocal threadLocalInstance =
            new ThreadLocal(){
                @Override
                protected ThreadLocalSingleton initialValue() {

                    return new ThreadLocalSingleton();
                }
            };

    private ThreadLocalSingleton(){};

    public static ThreadLocalSingleton getInstance(){
        return threadLocalInstance.get();
    }
}

优点:按线程进行隔离的,每个线程具有唯一的实例。原因可参考源码,该this是一个线程内的this

设计模式之单例模式_第4张图片

总结:单例模式的思考

1.需要考虑 内存的占用,资源的浪费

2.需要考虑 线程安全 问题

3.需要考虑 反射破坏单例问题

4.需要考虑 序列化破坏单例的问题

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