zoukankan      html  css  js  c++  java
  • java设计模式1——单例模式

    java设计模式1——单例模式

    1、单例模式介绍

    1.1、核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点

    1.2、常见场景

    1.3、单例模式的优点

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

    2、饿汉式

    2.1、第一步:私有化构造器。(防止外部直接new对象)

    //保证类只有一个实例,私有其构造器
    private SingletonDemo01() {
    
    }
    

    2.2、第二步:创建自身对象。

    //创建自身对象
    private static SingletonDemo01 instance = new SingletonDemo01();
    

    2.3、第三步:提够对外全局公开的方法

    //全局公开的方法
    public static SingletonDemo01 getInstance() {
        return instance;
    }
    

    2.4、测试是否为单例

    class SingletonDemo01Test {
        public static void main(String[] args) {
            SingletonDemo01 instance = SingletonDemo01.getInstance();
            SingletonDemo01 instance2 = SingletonDemo01.getInstance();
    
            System.out.println(instance.hashCode());
            System.out.println(instance2.hashCode());
            System.out.println(instance == instance2);
        }
    }
    

    输出的结果为:

    356573597
    356573597
    true
    

    2.5、弊端分析:

    饿汉式一上来就会对对象进行创建,不管后续有没有用到,如果对于较大内存的对象而后续也都没有用到,则会造成较大的内存空间的浪费。

    2.6、本类全部代码

    package com.xgp.company.第一种_单例模式.饿汉式;
    
    /**
     *
     * 核心:保证一个类只有一个实例,并且提供一个范围该实例的全局访问点
     */
    public class SingletonDemo01 {
    
        //保证类只有一个实例,私有其构造器
        private SingletonDemo01() {
    
        }
    
        //创建自身对象
        private static SingletonDemo01 instance = new SingletonDemo01();
    
        //全局公开的方法
        public static SingletonDemo01 getInstance() {
            return instance;
        }
    
    }
    
    class SingletonDemo01Test {
        public static void main(String[] args) {
            SingletonDemo01 instance = SingletonDemo01.getInstance();
            SingletonDemo01 instance2 = SingletonDemo01.getInstance();
    
            System.out.println(instance.hashCode());
            System.out.println(instance2.hashCode());
            System.out.println(instance == instance2);
        }
    }
    

    3、懒汉式

    目的:解决饿汉式可能存在的内存空间浪费的问题进行该进,不一上来就创建对象,而是在使用时再来创建对象。

    3.1、懒汉式的代码如下:

    public class SingletonDemo02 {
    
        //保证类只有一个实例,私有其构造器
        private SingletonDemo02() {
    
        }
        //创建自身对象,当时不用立即加载
        private static SingletonDemo02 instance;
    
        //全局公开的方法  synchronized作用:加锁  多线程进来时会不安全,效率较低
        public static synchronized SingletonDemo02 getInstance() {
            if(instance == null) {
                instance = new SingletonDemo02();
            }
            return instance;
        }
    
    }
    

    3.2、分析:代码中为什要使用synchronized关键字来进行上锁

    考率一下多线程的情况下,如果没有上锁,两个线程A、B一前以后的很紧密的执行该方法,而此时A完成了初始化操作,但是还没有进行返回,B此时进入判断语句中,此时也为null,这样也会进行初始化操作,于是乎,就得到了两个对象了,违反了单例模式设计得原则。

    3.3、弊端分析:

    该方法使用了synchronized对一个返回得方法进行了上锁,该方法得执行效率会较慢。

    4、DCL_懒汉式

    目的:DCL_懒汉式又称为双重检测懒汉式,为了改进懒汉式效率不高的问题

    4.1、该类的1版本的代码如下:

    public class SingletonDemo03 {
    
        //保证类只有一个实例,私有其构造器
        private SingletonDemo03() {
        }
        //创建自身对象,当时不用立即加载 volatile作用:尽大可能的解决极端情况的问题
        private volatile static SingletonDemo03 instance;
    
        //全局公开的方法  synchronized作用:加锁  多线程进来时会不安全,效率较低
        public static SingletonDemo03 getInstance() {
            if(instance == null) {
                //定一次进来时加锁,后面进来时就不加锁了,提高了效率
                synchronized (SingletonDemo03.class) {
                    if(instance == null) {
                        instance = new SingletonDemo03();
                    }
                }
            }
            return instance;
        }
    
    }
    

    4.2、分析1版本代码:

    同样考率多线程的情况下,A、B两线程相继的进入方法中,A率先获得初始化权力,进行上锁,进行对对象的创建,并且因为有volatile关键字,能够快速的将对象更新给B。如果B未进入判断语句中,则此时B中有该类对象了,直接返回了。如果B进入了判断语句中,但是A已经上锁了,也无法进入了,只有返回了。

    4.3、1版本的弊端

    1、再考虑多线程的极端情况,如果该类比较庞大,创建对象需要花费很长时间,B已经进入函数中了,而A创建对象的时间会比B走完该函数的时间长,则此时该函数将会返回B,而B=NULL。

    2、该模式无法防止反射

    4.4、版本2代码:

    public class SingletonDemo03 {
    
        //破坏两次都用反射创建对象
        private static boolean flag = false;
    
        //保证类只有一个实例,私有其构造器
        private SingletonDemo03() {
            //防治被反射
            synchronized (SingletonDemo03.class) {
                if(flag == false) {
                    flag = true;
                }else {
                    throw new RuntimeException("不要试图用反射破坏单例");
                }
            }
    
        }
        //创建自身对象,当时不用立即加载 volatile作用:尽大可能的解决极端情况的问题
        private volatile static SingletonDemo03 instance;
    
        //全局公开的方法  synchronized作用:加锁  多线程进来时会不安全,效率较低
        public static SingletonDemo03 getInstance() {
            if(instance == null) {
                //定一次进来时加锁,后面进来时就不加锁了,提高了效率
                synchronized (SingletonDemo03.class) {
                    if(instance == null) {
                        instance = new SingletonDemo03();
                    }
                }
            }
            return instance;
        }
    
    }
    

    4.5、弊端分析

    该版本同样未解决上面的问题,只是加大了反射获取对象的难度,反射破坏单例的代码如下:

    class SingletonDemo03Test {
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
            /*
            SingletonDemo03 instance1 = SingletonDemo03.getInstance();
            SingletonDemo03 instance2 = SingletonDemo03.getInstance();
    
            System.out.println(instance1 == instance2);
    */
            Class<SingletonDemo03> clazz = SingletonDemo03.class;
    
            //反射破坏单例
            Constructor<SingletonDemo03> declaredConstructor = clazz.getDeclaredConstructor(null);
    
            declaredConstructor.setAccessible(true);
    
            SingletonDemo03 instance1 = declaredConstructor.newInstance();
    
            //破坏flag
            Field flag = clazz.getDeclaredField("flag");
            flag.setAccessible(true);
            flag.set(clazz,false);
            System.out.println(flag.get(clazz));
    
    
            SingletonDemo03 instance2 = declaredConstructor.newInstance();
    
            System.out.println(instance1 == instance2);
    
            System.out.println(instance1.hashCode());
            System.out.println(instance2.hashCode());
        }
    }
    

    运行结果:

    false
    false
    21685669
    2133927002
    

    5、静态内部类实现

    该方式能够不适用synchronized提高效率,并且能够保证在多线程的情况下依旧是单例,代码如下:

    public class SingletonDemo04 {
        private SingletonDemo04() {
    
            //防治被反射
            synchronized (SingletonDemo04.class) {
                if(InnerClass.instance != null) {
                    throw new RuntimeException("不要试图用反射破坏单例");
                }
            }
        }
    
        private static class InnerClass {
            private static final SingletonDemo04 instance = new SingletonDemo04();
        }
    
        public static SingletonDemo04 getInstance() {
            return InnerClass.instance;
        }
    }
    

    6、利用枚举来实现

    java中最为推荐的是使用枚举类来创建单例对象,因为枚举类有这纯天然的优势,无法被反射。点击进反射创建对象的newInstance()方法的源码中可以发现:

    @CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, 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;
    }
    

    此外,枚举类也本身就是单例的,所以使用枚举类来创建单例对象最为适合,而如今大多数的框架的单例也都是通过这样的方法进行创建的。代码如下:

    /**
     * 反射不能破坏枚举类型,枚举类纯天然的单例,最简单
     */
    public enum SingletonDemo05 {
        INSTANCE;
    
        public SingletonDemo05 getInstance() {
            return INSTANCE;
        }
    
        public String hello() {
            return "Hello World!";
        }
    }
    
    class SingletonDemo05Test {
        public static void main(String[] args) {
            SingletonDemo05 instance1 = SingletonDemo05.INSTANCE;
            SingletonDemo05 instance2 = SingletonDemo05.INSTANCE.getInstance();
    
            System.out.println(instance1 == instance2);
    
            String hello = SingletonDemo05.INSTANCE.hello();
            System.out.println(hello);
        }
    }
  • 相关阅读:
    Hadoop命令手册
    编程算法
    综合8种子排序算法总结和比较
    android 创建一个新的每次project什么时候 请问自己主动 参加 V7依赖?
    【JDBC】java PreparedStatement操作oracle数据库
    【cocos2dx 加载资源目录】
    Project Euler:Problem 39 Integer right triangles
    矿Java开发学习之旅------&gt;Java排序算法经典的二分法插入排序
    [React Intl] Render Content with Placeholders using react-intl FormattedMessage
    [React Intl] Install and Configure the Entry Point of react-intl
  • 原文地址:https://www.cnblogs.com/xgp123/p/12262160.html
Copyright © 2011-2022 走看看