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

    单例模式(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());
        }
    }

    枚举写法简单,而且不会被反射破坏
  • 相关阅读:
    SQL Azure (17) SQL Azure V12
    Microsoft Azure News(5) Azure新DV2系列虚拟机上线
    Azure Redis Cache (3) 在Windows 环境下使用Redis Benchmark
    Azure PowerShell (11) 使用自定义虚拟机镜像模板,创建Azure虚拟机并绑定公网IP(VIP)和内网IP(DIP)
    Windows Azure Virtual Machine (31) 迁移Azure虚拟机
    Windows Azure Web Site (16) Azure Web Site HTTPS
    Azure China (12) 域名备案问题
    一分钟快速入门openstack
    管理员必备的Linux系统监控工具
    Keepalived+Nginx实现高可用和双主节点负载均衡
  • 原文地址:https://www.cnblogs.com/xiexiaofei/p/14134514.html
Copyright © 2011-2022 走看看