12.单例模式

单例模式分为
    1.饿汉式
    2.懒汉式
基本模式:构造器私有,提供对应的静态方法去获取!

1.饿汉式样例:
    public class Hungry {
        //重点1:构造器私有,外部将无法创建该对象
        private Hungry(){
        }
        //重点2:自己直接创建一个该对象,引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的
        private final static Hungry HUNGRY = new Hungry();
        //重点3;提供对应的获取方法
        private static Hungry getInstance(){
            return HUNGRY;
        }
    }
    懒汉式总结:
        1.直接在类中创建该对象,可能会造成资源浪费,因为如果该类在代码中没有用到,但是该类在加载时已经创建了对象,这样就造成了内存浪费!

2.懒汉式样例:
    public class LazyMan {
        //重点1:构造器私有
        private LazyMan(){
        }
        //重点2:创建LazyMan的变量,并没有具体引用,这是区别于饿汉式的地方!避免了浪费资源,因为如果该类没有用的话,不创建,调用方法时再创建!
        private static LazyMan lazyMan;
        public static LazyMan GetInstance(){
            //重点3:先判断是否为null,再决定是否创建!
            if (lazyMan == null) {
                lazyMan = new LazyMan();
            }
            return lazyMan;
        }
    }

3.静态内部类创建单例模式
    public class Holder {
        重点1:构造器私有
        private Holder(){
            
        }
        //重点4:创建public方法获取内部类属性!
        public static Holder getInstance(){
            return InnnerClass.HOLDER;
        }
        重点2:创建内部类
        public static class InnnerClass{
            重点3:私有的创建内部类属性
            private static final Holder HOLDER=new Holder();
        }
    }

--------------------------------------------------------------------------------
    

3.饿汉式的并发问题:

饿汉式的并发问题!
    public class LazyMan {
        private LazyMan(){
            //重点1:私有的构造器方法中打印该句!
            System.out.println(Thread.currentThread().getName()+"懒汉式创建对象!");
        }
        private static LazyMan lazyMan;
        public static LazyMan GetInstance(){
            if (lazyMan == null) {
                lazyMan = new LazyMan();
            }
            return lazyMan;
        }
        //重点2:启动20个线程创建该对象
        public static void main(String[] args) {
            for (int i = 0; i < 20; i++) {
                new Thread(()->{
                    LazyMan.GetInstance();
                },"线程"+i).start();
            }
        }
    }
    输出:发现有并发执行的问题,即创建的对象可能不是单例的!
        线程2懒汉式创建对象!
        线程3懒汉式创建对象!
        线程1懒汉式创建对象!


2.如何解决呢:
    方法1:加单层锁:
        public static LazyMan GetInstance() {
            重点1:锁住的是class类
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
            return lazyMan;
        }
    方法2:双重检测锁模式 又称之为DCL更安全!
        public static LazyMan GetInstance() {
            //重点1:加一层if判断
            if (lazyMan == null) {
                //重点2:加锁,锁住类,这个类的所有对象将使用同一把锁!
                synchronized (LazyMan.class) {
                    if (lazyMan == null) {
                        lazyMan = new LazyMan();//不是原子性操作
                        会经历三个步骤:
                            1.分配内存空间
                            2.执行构造方法,初始化对象
                            3.把这个对象指向这个空间
                            这里可能会发生指令重排(JVM底层执行时,在不影响其结果的情况下,会优化执行顺序,例如132执行顺序!)
                    }
                }
            }
            return lazyMan;
        }
        问题:创建对象在底层并不是个原子性操作,可能会发生指令重排,所以方法2也不是个安全的方法!

    方法3:DCL模式和volatile 原子性操作
        重点1:使用volatile 避免指令重排
        private static volatile LazyMan lazyMan;
        重点2:再使用DCL双重检测锁!
        public static LazyMan GetInstance() {
            if (lazyMan == null) {
                synchronized (LazyMan.class) {
                    if (lazyMan == null) {
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
反射可以破坏上述的所有安全单例模式

posted @ 2022-05-13 21:21  努力的达子  阅读(27)  评论(0编辑  收藏  举报