• 单例设计模式


    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

    这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

    注意:

    • 1、单例类只能有一个实例。
    • 2、单例类必须自己创建自己的唯一实例。
    • 3、单例类必须给所有其他对象提供这一实例。

    上面这几句引自https://www.runoob.com/design-pattern/singleton-pattern.html

    众所周知,单例模式分为懒汉式和饿汉式。下面我逐一介绍。

    饿汉式:

    public class HungryMan {
    
        private HungryMan(){}
    
        private static final HungryMan HUNGRY_MAN = new HungryMan();
    
        public static HungryMan getInstance(){
            return HUNGRY_MAN;
        }
    
        public static void main(String[] args) {
            HungryMan hungryMan1 = HungryMan.getInstance();
            HungryMan hungryMan2 = HungryMan.getInstance();
            System.out.println(hungryMan1.hashCode());
            System.out.println(hungryMan2.hashCode());
        }
    }
    说明创建的是同一个对象

    但是饿汉式会造成资源的浪费
    懒汉式:

    public class LazyMan {
    
        private LazyMan(){}
    
        public static LazyMan lazyMan;
    
        public static LazyMan getInstance(){
            if(lazyMan == null){
                lazyMan = new LazyMan();
                return lazyMan;
            }
            return lazyMan;
        }
    
        public static void main(String[] args) {
            LazyMan lazyMan1 = LazyMan.getInstance();
            LazyMan lazyMan2 = LazyMan.getInstance();
            System.out.println(lazyMan1.hashCode());
            System.out.println(lazyMan2.hashCode());
        }
    }
    看到两个对象是一样的

    但是多线程的情况下懒汉式是不安全的

    检验不安全:

    public class LazyMan {
    
        private LazyMan(){}
    
        public static LazyMan lazyMan;
    
        public static LazyMan getInstance(){
            if(lazyMan == null){
                //睡一会
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lazyMan = new LazyMan();
                return lazyMan;
            }
            return lazyMan;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 100; i++) {
                new Thread(()->{
                    LazyMan instance = LazyMan.getInstance();
                    System.out.println(instance.hashCode());
                }).start();
            }
        }
    }

    哈希值是不一样的,所以懒汉式在并发的情况下是不安全的

    加了锁的懒汉式
    public class LazyMan {
    
        private LazyMan(){}
    
        public static LazyMan lazyMan;
    
        public static LazyMan getInstance(){
            if(lazyMan == null){
                synchronized(LazyMan.class){
                    if(lazyMan == null){
                        lazyMan = new LazyMan();
                    }
                }
                return lazyMan;
            }
            return lazyMan;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 100; i++) {
                new Thread(()->{
                    LazyMan instance = LazyMan.getInstance();
                    System.out.println(instance.hashCode());
                }).start();
            }
        }
    }

    延迟一秒的结果(代码中没有体现)

     
    线程A执行到3的位置,线程B抢到执行权,停留在1的位置,因为有锁不能继续执行,线程A创建对象,线程B即使能执行到2的位置,但是因为对象不是null,还是不能执行到3的位置,不能创建新的对象。

    但是,另一个问题来了
    public static LazyMan getInstance(){
        if(lazyMan == null){
            synchronized(LazyMan.class){
                if(lazyMan == null){
                    lazyMan = new LazyMan();
                    /**
                     * lazyMan = new LazyMan();
                     * 但是这句话并不是一个原子性的,它有三个步骤
                     * 1、开辟一段内存地址
                     * 2、执行构造函数,初始化对象
                     * 3、将对象指向这段内存地址
                     * 完美的情况下是  1 2 3 ,但是可能 万一是 1 3 2
                     * 开辟完内存地址,指向这段内存地址,初始化对象
                     * 此时的初始化对象已经无济于事,因为已经指向内存地址了
                     * 这就导致,不是原子性的问题带来的影响
                     */
                }
            }
            return lazyMan;
        }
        return lazyMan;
    }

    所以我们需要加上volatile

    但是我们依旧可以通过反射来破坏
    package com.xiaofei.single;
    
    import java.lang.reflect.Constructor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author xiaofei
     * @version 1.0
     * @date 2020/9/12 18:47
     */
    
    public class LazyMan {
    
        private LazyMan(){}
    
        public volatile static LazyMan lazyMan;
    
        public static LazyMan getInstance(){
            if(lazyMan == null){
                synchronized(LazyMan.class){
                    if(lazyMan == null){
                        lazyMan = new LazyMan();
                    }
                }
                return lazyMan;
            }
            return lazyMan;
        }
    
        public static void main(String[] args) throws Exception {
            LazyMan lazyMan1 = LazyMan.getInstance();
            Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
            constructor.setAccessible(true);
            LazyMan lazyMan2 = constructor.newInstance();
            System.out.println(lazyMan1.hashCode());
            System.out.println(lazyMan2.hashCode());
        }
    }

    可以发现,反射依旧可以破坏

    下面我们用到终极办法:枚举
    public enum  SingleEnum {
    
        SINGLENUM;
    }
    class Test{
        public static void main(String[] args) {
            SingleEnum singlenum1 = SingleEnum.SINGLENUM;
            SingleEnum singlenum2 = SingleEnum.SINGLENUM;
            System.out.println(singlenum1.hashCode());
            System.out.println(singlenum2.hashCode());
        }
    }

    枚举写法简单,而且不会被反射破坏
  • 相关阅读:
    VGA实验 :逻辑分析仪
    VGA实验:点亮屏幕
    ASP.NET 4‎.0 生成 eurl.axd Http异常错误的处理方法 (汗IIS 怎这么多莫名其妙的问题)
    腾讯检测 IP 省市的接口
    hql 中cast 方法的使用
    asp.net 防止外部提交数据(转)
    为 SQL Server 启用缓存通知
    net中前台 javascript与后台c#函数相互调用
    const 与 readonle 的异同
    获取页面执行时间的几种方法(asp.net转)
  • 原文地址:https://www.cnblogs.com/xiexiaofei/p/14134514.html
Copyright © 2020-2023  润新知