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

    什么是单例模式(Singleton) 设计模式的一种 一个类只有一个实例对象

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

    为什么要使用单例模式

    有些类频繁创建和销毁消耗资源, 可以完全复用,比如IO处理,数据库操作,全局用一个类就好了.

    所以我们将这个类设置成单例类,全局只有一个,一次创建就可重复使用.

    单例模式的写法

    1.饿汉式 静态常量

    2.饿汉式 静态代码块

    3.懒汉式

    4.懒汉式 + synchronized

    5.懒汉式 + 双重检锁

    6.懒汉式 + 双重检锁 + volatile

    7.静态内部类

    8.枚举

    写法1.饿汉式 静态常量

    public class HungryMan {
    
        private HungryMan(){}
    
        private static HungryMan hungryMan = new HungryMan();
    
        public static HungryMan getInstance(){
            return hungryMan;
        }
    }

    写法2.饿汉式 静态代码块

    public class HungryMan {
    
        private static HungryMan hungryMan;
    
        static {
            hungryMan = new HungryMan();
        }
    
        private  HungryMan(){}
    
        public static HungryMan getInstance() {
            return hungryMan;
        }
    }

     说明下

    懒汉式:指全局的单例实例在第一次被使用时构建。

    饿汉式:指全局的单例实例在类装载时构建。

    从它们的区别也能看出来,日常我们使用的较多的应该是懒汉式的单例,毕竟按需加载才能做到资源的最大化利用

    写法3.懒汉式

    public class LazyMan {
    
        private LazyMan(){}
    
        private static LazyMan lazyMan;
    
        public static  LazyMan getInstance(){
            if (lazyMan == null) {
               lazyMan = new LazyMan();
            }
            return lazyMan;
        }
    }

    说明下

    但是懒汉式是线程不安全的,多个线程访问会出现问题

    /**
     * @description: 懒汉式 单线程可用
     */
    public class LazyMan {
    
        private LazyMan(){
            System.out.println("运行线程 = " + Thread.currentThread().getName());
        }
    
        private static LazyMan lazyMan;
    
        public static  LazyMan getInstance(){
            if (lazyMan == null) {
               lazyMan = new LazyMan();
            }
            return lazyMan;
        }
    }
    
    /***
     * 模拟多线程访问单例类   懒汉式线程不安全   因为多线程情况下 会构建多个对象
     */
    class Test{
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    LazyMan.getInstance();
                }).start();
            }
        }
    }

    运行结果如下

     说明下 解决写法3 问题 使用写法4

    4.懒汉式 + synchronized

    /**
     * @description:懒汉式 + synchronized
     */
    public class LazyMan {
    
        private LazyMan() {
            System.out.println("运行线程 = " + Thread.currentThread().getName());
        }
    
        private static LazyMan lazyMan;
    
        public static synchronized LazyMan getInstance() {
            if (lazyMan == null) {
                lazyMan = new LazyMan();
            }
            return lazyMan;
        }
    }
    
    class Test {
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    LazyMan.getInstance();
                }).start();
            }
        }
    }

    运行结果如下

    说明下 写法4 性能低 不推荐使用  解决使用写法5

    写法5.懒汉式 + 双重检锁

    /**
     * @description: 懒汉式 + 双重检锁 
     */
    public class LazyMan {
    
        private LazyMan() {
            System.out.println("运行线程 = " + Thread.currentThread().getName());
        }
    
        private static LazyMan lazyMan;
    
        public static LazyMan getInstance() {
            if (lazyMan == null) {
                synchronized (LazyMan.class) {
                    if (lazyMan == null) {
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
    }
    
    class Test {
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                   LazyMan.getInstance();
                }).start();
            }
        }
    }

    运行结果如下

     说明下

     new LazyMan(); 不是原子性操作

    需要完成以下几步 1 2 3

        1.分配空间

        2.执行构造方法,初始化构造

        3.对象指向内存地址

      JVM虚拟机为了执行效率可能对以上操作进行指令重排 原来的123可能编程132

      多线程情况下 还是不安全的  解决使用写法6

    写法6.懒汉式 + 双重检锁 + volatile

    /**
     * @description: 懒汉式 + 双重检锁  + volatile关键字 修饰  避免指令重排
     */
    public class LazyMan {
    
        private LazyMan() {
            System.out.println("运行线程 = " + Thread.currentThread().getName());
        }
    
        private volatile static LazyMan lazyMan;
    
        public static LazyMan getInstance() {
            if (lazyMan == null) {
                synchronized (LazyMan.class) {
                    if (lazyMan == null) {
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
    }
    
    class Test {
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                   LazyMan.getInstance();
                }).start();
            }
        }
    }

    运行结果如下

    写法7.使用静态内部类

    静态内部类实现单例
    /**
     * @description: 静态内部类
     */
    public class Holder {
        private Holder (){}
    
        public static Holder getInstance(){
            return innerClass.holder;
        }
    
        public static class innerClass{
            private static final Holder holder = new Holder();
        }
    }

     说明下

    还有一个问题就是反射可以破坏单例

    情况一     一个使用getInstance获得单例对象  一个通过反射获得对象

    /**
     * @description: 懒汉式 + 双重检锁  + volatile关键字 修饰
     */
    public class LazyMan {
    
        private LazyMan() {
            System.out.println("运行线程 = " + Thread.currentThread().getName());
        }
    
        private volatile static LazyMan lazyMan;
    
        public static LazyMan getInstance() {
            if (lazyMan == null) {
                synchronized (LazyMan.class) {
                    if (lazyMan == null) {
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
    }
    
    class TestReflect {
        /***
         * 使用反射会破坏单例
         * @param args
         */
        public static void main(String[] args) throws Exception {
            LazyMan instance = LazyMan.getInstance();
            //使用反射来获取单例对象
            Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
            constructor.setAccessible(true);
            LazyMan instance2 = constructor.newInstance();
            System.out.println(instance);
            System.out.println(instance2);
        }
    }

    运行结果如下

     说明下 

    通过反射破坏了单例  创建了两个对象

    解决加锁 手动抛出异常

    public class LazyMan {
    
        private LazyMan() {
            synchronized (LazyMan.class){
                if (lazyMan != null) {
                    throw new RuntimeException("禁止使用反射破坏单例");
                }
            }
        }
    
        private volatile static LazyMan lazyMan;
    
        public static LazyMan getInstance() {
            if (lazyMan == null) {
                synchronized (LazyMan.class) {
                    if (lazyMan == null) {
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
    }
    
    class TestReflect {
        /***
         * 使用反射会破坏单例
         * @param args
         */
        public static void main(String[] args) throws Exception {
            LazyMan instance = LazyMan.getInstance();
            //使用反射来获取单例对象
            Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
            constructor.setAccessible(true);
            LazyMan instance2 = constructor.newInstance();
            System.out.println(instance);
            System.out.println(instance2);
        }
    }

    运行结果如下

    情况二     直接通过反射创建两个单例对象

    /**
     * @description: 懒汉式 + 双重检锁  + volatile关键字 修饰 + synchronized
     */
    public class LazyMan {
    
        private LazyMan() {
            synchronized (LazyMan.class){
                if (lazyMan != null) {
                    throw new RuntimeException("禁止使用反射破坏单例");
                }
            }
        }
    
        private volatile static LazyMan lazyMan;
    
        public static LazyMan getInstance() {
            if (lazyMan == null) {
                synchronized (LazyMan.class) {
                    if (lazyMan == null) {
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
    }
    
    class TestReflect {
        /***
         * 使用反射会破坏单例
         * @param args
         */
        public static void main(String[] args) throws Exception {
            //直接通过反射创建两个单例对象
            Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
            constructor.setAccessible(true);
            LazyMan instance2 = constructor.newInstance();
            LazyMan instance3= constructor.newInstance();
            System.out.println(instance2);
            System.out.println(instance3);
        }
    }

    运行结果如下

     说明下

    还是出现了两个对象破坏了单例

    解决设置标志位

    /**
     * @description: 懒汉式 + 双重检锁  + volatile关键字 修饰 + synchronized +标志位
     */
    public class LazyMan {
    
        //设置标志位
        private static boolean flag = false;
        private LazyMan() {
            synchronized (LazyMan.class){
                if (flag == false){
                    flag = rue;
                }else {
                    throw new RuntimeException("禁止使用反射破坏单例");
                }
            }
        }
    
        private volatile static LazyMan lazyMan;
    
        public static LazyMan getInstance() {
            if (lazyMan == null) {
                synchronized (LazyMan.class) {
                    if (lazyMan == null) {
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
    }
    
    class TestReflect {
        /***
         * 使用反射会破坏单例
         * @param args
         */
        public static void main(String[] args) throws Exception {
            //直接通过反射创建两个单例对象
            Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
            constructor.setAccessible(true);
            LazyMan instance2 = constructor.newInstance();
            LazyMan instance3= constructor.newInstance();
            System.out.println(instance2);
            System.out.println(instance3);
        }
    }

    运行结果如下

     情况三   直接通过反射创建两个单例对象  破坏标志位

    /**
     *
     * @description: 懒汉式 + 双重检锁  + volatile关键字 修饰
     */
    public class LazyMan {
    
        //设置标志位
        private static boolean flag = false;
        private LazyMan() {
            synchronized (LazyMan.class){
                if (flag == false){
                    flag = true;
                }else {
                    throw new RuntimeException("禁止使用反射破坏单例");
                }
            }
        }
    
        private volatile static LazyMan lazyMan;
    
        public static LazyMan getInstance() {
            if (lazyMan == null) {
                synchronized (LazyMan.class) {
                    if (lazyMan == null) {
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
    }
    
    class TestReflect {
        /***
         * 使用反射会破坏单例  破坏标志位
         * @param args
         */
        public static void main(String[] args) throws Exception {
            //破坏标志位
            Field flag = LazyMan.class.getDeclaredField("flag");
            flag.setAccessible(true);
    
            //直接通过反射创建两个单例对象
            Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
            constructor.setAccessible(true);
            LazyMan instance2 = constructor.newInstance();
    
            flag.set(instance2,false);
    
            LazyMan instance3= constructor.newInstance();
            System.out.println(instance2);
            System.out.println(instance3);
        }
    }

    运行结果如下

     说明下

    通过反射 破坏标志位 仍然能破坏单例

    分析

    在Constructor.getInstance 方法中 的这段代码说明反射不能破坏枚举的 引出写法8解决

    写法8.枚举

    /**
     * 使用枚举
     */
    public enum EnumSingle {
    
        INSTANCE;
        public EnumSingle getInstance() {
            return INSTANCE;
        }
    }
    class Test{
        public static void main(String[] args) {
            EnumSingle instance = EnumSingle.INSTANCE;
            EnumSingle instance2 = EnumSingle.INSTANCE;
            System.out.println(instance);
            System.out.println(instance2);
    
        }
    }

    运行结果如下

      //验证反射不能破坏枚举   

    Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);

    constructor.setAccessible(true);

    EnumSingle enumSingle = constructor.newInstance();

    EnumSingle enumSingle2 = constructor.newInstance();

    运行结果如下 和在Constructor.getInstance 方法中 抛出的异常一样

     说明下

    所有枚举都继承Enum  

    它的构造方法有两个参数

    古人学问无遗力,少壮工夫老始成。 纸上得来终觉浅,绝知此事要躬行。
  • 相关阅读:
    UVA 1386
    疯狂Android演讲2 环境配置
    七内部排序算法汇总(插入排序、Shell排序、冒泡排序、请选择类别、、高速分拣合并排序、堆排序)
    【iOS发展-44】通过案例谈iOS重构:合并、格式化输出、宏观变量、使用数组来存储数据字典,而且使用plist最终的知识
    jQuery选择
    一个月操作总结
    C++易vector
    oracle rac 在完成安装错误。
    NginX issues HTTP 499 error after 60 seconds despite config. (PHP and AWS)
    解决Eclipse中文乱码的方法
  • 原文地址:https://www.cnblogs.com/wf-zhang/p/14529825.html
Copyright © 2011-2022 走看看