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

    一、饿汉式单例

    饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线
    程还没出现以前就是实例化了,不可能存在访问安全问题。
    优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
    缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅
    坑不拉屎。

    Spring中IOC容器ApplicationContext本身就是典型的饿汉式单例。接下来看一段代
    码:

    //饿汉式单例
    // 它是在类加载的时候就立即初始化,并且创建单例对象

    //优点:没有加任何的锁、执行效率比较高,
    //在用户体验上来说,比懒汉式更好

    //缺点:类加载的时候就初始化,不管你用还是不用,我都占着空间
    //浪费了内存,有可能占着茅坑不拉屎

    //绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题
    public class HungrySingleton {
    //先静态、后动态
    //先属性、后方法
    //先上后下
    private static final HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){}

    public static HungrySingleton getInstance(){
    return hungrySingleton;
    }
    还有另外一种写法,利用静态代码块的机制:
    //饿汉式静态块单例
    public class HungryStaticSingleton {
    private static final HungryStaticSingleton hungrySingleton;
    static {
    hungrySingleton = new HungryStaticSingleton();
    }
    private HungryStaticSingleton(){}
    public static HungryStaticSingleton getInstance(){
    return hungrySingleton;
    }
    }

    二、懒汉式单例
    懒汉式单例的特点是:被外部类调用的时候内部类才会加载,下面看懒汉式单例的简单
    实现LazySimpleSingleton:
    //懒汉式单例
    //在外部需要使用的时候才进行实例化 线程不安全
    public class LazySimpleSingleton {
    private LazySimpleSingleton(){}
    //静态块,公共内存区域
    private static LazySimpleSingleton lazy = null;
    public static LazySimpleSingleton getInstance(){
    if(lazy == null){
    lazy = new LazySimpleSingleton();
    }
    return lazy;
    }
    }

    实现线程安全的单例:
    //懒汉式单例
    //在外部需要使用的时候才进行实例化
    public class LazySimpleSingleton {
    private LazySimpleSingleton(){}
    //静态块,公共内存区域
    private static LazySimpleSingleton lazy = null;
    //加锁,保证线程安全,但是锁住的粒度太大,导致性能低但是,用
    //synchronized加锁,在线程数量比较多情况下,如果CPU 分配压力上升,会导致大批
    //量线程出现阻塞,从而导致程序运行性能大幅下降。
    public synchronized static LazySimpleSingleton getInstance(){
    if(lazy == null){
    lazy = new LazySimpleSingleton();
    }
    return lazy;
    }
    }
    双重校验方式实现单例:
    public class LazyDoubleCheckSingleton {
    //volatile是为了让该变量有可见性以及禁止重排序
    private volatile static LazyDoubleCheckSingleton lazy = null;

    private LazyDoubleCheckSingleton(){}
    public static LazyDoubleCheckSingleton getInstance(){
    if(lazy == null){
    synchronized (LazyDoubleCheckSingleton.class){
    if(lazy == null){
    lazy = new LazyDoubleCheckSingleton();//第7行
    //1.分配内存给这个对象
    //2.初始化对象
    //3.设置lazy指向刚分配的内存地址
    //4.初次访问对象
    }
    }
    }
    return lazy;
    }
    }
    双重校验锁为什么要加volatile关键字修饰?
     lazy = new LazyDoubleCheckSingleton();//第7行这个创建了一个对象,这一行代码可以分解为
    如下的3行伪代码。
    memory=allocate();//1.分配对象的内存空间
    ctorInstance(memory);//2初始化对象
    instance=memory;//3.设置instance指向刚分配的内存地址
    上面3行伪代码中的2和3之间,可能会被重排序,2和3之间重排序之后的执行时序如下:
    1 3 2
    由于可能存在指令重排序,即线程A执行了第3步,没执行第2步,即对象并未初始化完成,导致线程B在判断对象是否为空时,获取到了一个未完全初始化完成的对象,所以要通过volatile来禁止指令重排序;
     
    
    
    //这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题
    //完美地屏蔽了这两个缺点
    //史上最牛B的单例模式的实现方式
    public class LazyInnerClassSingleton {
    //默认使用LazyInnerClassGeneral的时候,会先初始化内部类
    //如果没使用的话,内部类是不加载的
    private LazyInnerClassSingleton(){
    if(LazyHolder.LAZY != null){
    throw new RuntimeException("不允许创建多个实例");
    }
    }

    //每一个关键字都不是多余的
    //static 是为了使单例的空间共享
    //保证这个方法不会被重写,重载
    public static final LazyInnerClassSingleton getInstance(){
    //在返回结果以前,一定会先加载内部类
    return LazyHolder.LAZY;
    }

    //默认不加载
    private static class LazyHolder{
    private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
    }

    三:容器式单例
    //Spring中的做法,就是用这种注册式单例
    public class ContainerSingleton {
    private ContainerSingleton(){}
    private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
    public static Object getInstance(String className){
    synchronized (ioc) {
    if (!ioc.containsKey(className)) {
    Object obj = null;
    try {
    obj = Class.forName(className).newInstance();
    ioc.put(className, obj);
    } catch (Exception e) {
    e.printStackTrace();
    }
    return obj;
    } else {
    return ioc.get(className);
    }
    }
    }
    }

    容器式写法适用于创建实例非常多的情况,便于管理。但是,是非线程安全的。到此,
    注册式单例介绍完毕。

    四、枚举单例
    //常量中去使用,常量不就是用来大家都能够共用吗?
    //通常在通用API中使用
    public enum EnumSingleton {
    INSTANCE;
    private Object data;
    public Object getData() {
    return data;
    }
    public void setData(Object data) {
    this.data = data;
    }
    public static EnumSingleton getInstance(){
    return INSTANCE;
    }
    }
     


  • 相关阅读:
    leetcode 279. Perfect Squares
    leetcode 546. Remove Boxes
    leetcode 312. Burst Balloons
    leetcode 160. Intersection of Two Linked Lists
    leetcode 55. Jump Game
    剑指offer 滑动窗口的最大值
    剑指offer 剪绳子
    剑指offer 字符流中第一个不重复的字符
    leetcode 673. Number of Longest Increasing Subsequence
    leetcode 75. Sort Colors (荷兰三色旗问题)
  • 原文地址:https://www.cnblogs.com/zpp1234/p/13184272.html
Copyright © 2011-2022 走看看