zoukankan      html  css  js  c++  java
  • 【创建型】- 单例模式

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

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

    实现方式一:饿汉模式

    package com.huang.pims.demo.patterns.singleton;
    
    public class HungerSingleton {
    
        private static HungerSingleton singleInstance = new HungerSingleton();
    
        private HungerSingleton() {
            super();
        }
    
        public static HungerSingleton getInstance() {
            return singleInstance;
        }
    
    }

    实现方式二:懒汉模式

    package com.huang.pims.demo.patterns.singleton;
    
    public class LazySingleton {
    
        private static LazySingleton singleInstance;
    
        private LazySingleton() {
            super();
        }
    
        public static LazySingleton getInstance() {
            if (null == singleInstance) { // 位置1
                singleInstance = new LazySingleton();
            }
            return singleInstance;
        }
    
    }

      懒汉模式,在单线程环境下,功能是正常的。但是在多线程环境下,就不能保证所有线程通过 LazySingleton.getInstance() 获得的对象是同一个对象了。

      假如:线程A运行到位置1处进行判空,cpu给的时间片用完了,切换到其他线程运行,比如线程B,它也在调用LazySingleton.getInstance(),并成功获取到了单例对象。当线程A再次获得cpu分配的时间片时,由于之前对singleInstance的判空结果为true,线程B会再次创建一个对象实例。这就使得线程A和线程B获得的并不是同一个对象。

      解决方案:使用synchronized关键字

    实现方式三:线程安全的懒汉模式

    package com.huang.pims.demo.patterns.singleton;
    
    public class SyncMethodSingleton {
    
        private static SyncMethodSingleton singleInstance;
    
        private SyncMethodSingleton() {
            super();
        }
      // 使用synchronized关键字修饰方法
        public static synchronized SyncMethodSingleton getInstance() {
            if(null == singleInstance) {
                singleInstance = new SyncMethodSingleton();
            }
            return singleInstance;
        }
        
    }

      如此,在多线程环境下,众多线程在调用LazySingleton.getInstance()时,得先尝试获取对象锁,获得锁的线程A才能继续往下执行,方法执行完毕之后线程A才会释放锁。线程A释放对象锁之前,其他调用LazySingleton.getInstance()的线程则全部阻塞。

      这就解决了懒汉模式在多线程环境下线程不安全的问题。

    实现方式四:基于双重校验、线程安全的懒汉模式

    package com.huang.pims.demo.patterns.singleton;
    
    public class SyncCodeSingleton {
    
        private static SyncCodeSingleton singletonInstance;
    
        private SyncCodeSingleton() {
            super();
        }
    
        public static SyncCodeSingleton getInstance() {
            if(null == singletonInstance) {// 第一步判空,不需要获得锁
                synchronized (SyncCodeSingleton.class) {
                    if(null == singletonInstance) {// 第二步判空,判断当前线程获得锁之前,是否已经有其他线程获取锁并实例化对象了
                        singletonInstance = new SyncCodeSingleton();
                    }
                }
            }
            return singletonInstance;
        }
        
    }

      此种实现方式有一个缺陷:指令重排序可能导致返回的是一个没有实例化的对象。 

      singletonInstance = new VolatileSingleton(); 在代码层面,这只是一行代码,但是JVM在执行该行代码的时候,会分三步执行

    1.   分配内存
    2.   使用SyncCodeSingleton的构造函数来初始化实例对象
    3.   将singletonInstance指向分配的内存空间(执行完这步 singletonInstance 就不再为 null 了)

      JVM为了提高自身的效率,可能会进行指令重排序,互换第二步和第三步的执行顺序。

      假设线程A运行到singletonInstance = new VolatileSingleton();时,刚执行完第一步和第三步,还没来得及初始化实例对象,而此时的singletonInstance已然不是null了。这时,如果有线程B运行到首次判空的地方,判断结果自然为false,那么线程B就会返回一个没有初始化的singletonInstance,从而导致获取单例对象失败。

      解决方案:使用volatile关键字修饰singletonInstance,禁止了指令重排序,从而保证,如果singletonInstance不为null,singletonInstance必然已经经过了初始化。

    package com.huang.pims.demo.patterns.singleton;
    
    public class VolatileSingleton {
       // 使用volatile关键字禁止指令重排序
        private static volatile VolatileSingleton singletonInstance;
    
        private VolatileSingleton() {
            super();
        }
    
        public static VolatileSingleton getInstance() {
            if (null == singletonInstance) {
                synchronized (SyncCodeSingleton.class) {
                    if (null == singletonInstance) {
                        singletonInstance = new VolatileSingleton();
                    }
                }
            }
            return singletonInstance;
        }
    
    }

    实现方式五:基于类初始化来实现单例模式

    package com.huang.pims.demo.patterns.singleton;
    
    public class InnerInitSingleton {
    
        private static class InnerClass {
            private static InnerInitSingleton singletonInstance = new InnerInitSingleton();
        }
    
        private InnerInitSingleton() {
            super();
        }
    
        public static InnerInitSingleton getInstance() {
            return InnerClass.singletonInstance;
        }
    
    }

      该种模式也有一种缺陷,就是可以使用Java反射机制来new出一个新的单例对象。

    实现方式六:基于枚举类实现单例模式

    public enum  EnumSingleton {

    SINGLETON_INSTANCE("hello");

    private String prop;

    EnumSingleton(String prop) {
    this.prop = prop;
    }
      // 省略setter、getter方法

    public static EnumSingleton getInstance() {
    return SINGLETON_INSTANCE;
    }

    }

      基于枚举类实现的单例模式,无法使用Java反射来生成一个新的实例对象。使用Java反射是会抛出Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects异常

    
    
    
  • 相关阅读:
    C语言编程题目(5)单字符的位处理 数据加密
    C语言编程题目(4)文件高级应用与字符串高级操作
    C语言编程题目(3)文件类型操作
    C语言编程题目(2)基本数据类型操作
    个人总结
    第十六周进度条
    十五周进度条
    《人月神话》阅读笔记03
    《人月神话》阅读笔记02
    第十四周进度条
  • 原文地址:https://www.cnblogs.com/517cn/p/10875901.html
Copyright © 2011-2022 走看看