前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >单例模式

单例模式

作者头像
海向
发布2019-12-20 16:45:17
6130
发布2019-12-20 16:45:17
举报
文章被收录于专栏:海向

原文地址为https://cloud.tencent.com/developer/article/1557494,转载请注明出处!

简介

单例模式主要是为了避免因为创建了多个实例造成资源的浪费,且多个实例由于多次调用容易导致结果出现错误,而使用单例模式能够保证整个应用中有且只有一个实例

要求

只需要三部分即可保证只有一个实例

  • 不允许被外部类 new 对象
  • 在本类中创建对象
  • 对外提供接口调用创建好的对象

实现思路

针对上述三个条件我们用三种方式来保证这种要求

  • 构造方法私有化
  • 在本类中 new 一个本类对象
  • 创建一个 public 方法返回上步创建好的对象以供外部调用

懒汉式单例模式

这种方式在类创建的时候就实例化对象,不存在多线程问题,缺点是提前创建对象,若未被使用会造成资源浪费。

代码语言:javascript
复制
public class SingletonIns {

    private static SingletonIns singletonIns = new SingletonIns();
    
    private SingletonIns() {}
    
    public static SingletonIns getInstance() {
        return singletonIns;
    }
}

饿汉式单例模式

饿汉式单例优缺点与懒汉式正好相反,如果不作处理会有并发的问题,但是按需实例化,能避免资源被浪费的情况出现。

以下是最佳实践的例子

代码语言:javascript
复制
public class SingletonIns {
    private static volatile SingletonIns singletonIns = null;
    private SingletonIns() {}
    public SingletonIns getInstance() {
        if (null == singletonIns) {
            synchronized (SingletonIns.class) {
                if (null == singletonIns) {
                    singletonIns = new SingletonIns();
                }
            }
        }
        return  singletonIns;
    }
}

以下这种方式效率较低,在jdk以前的版本中synchronized性能极差,后续有了自旋锁、偏量锁等优化才慢慢改善,现在仍然不建议这样使用单例模式

代码语言:javascript
复制
public class SingletonIns {
    private static SingletonIns singletonIns = null;
    private SingletonIns() {}
    public synchronized SingletonIns getInstance() {
        if (null == singletonIns) {
            singletonIns = new SingletonIns();
        }
        return  singletonIns;
    }
}

以下这种方式存在线程安全的问题,例如a线程进入if判断,b线程已经进入2处,会导致实例化两个不同的对象

代码语言:javascript
复制
public class SingletonIns {
    private static SingletonIns singletonIns = null;
    private SingletonIns() {}
    public SingletonIns getInstance() {
        if (null == singletonIns) { //1 有线程安全问题
            synchronized(SingletonIns.class) {
                singletonIns = new SingletonIns();//2
            }
        }
        return  singletonIns;
    }
}

以下这种方式使用双重校验来避免上一种线程安全问题的出现,但是仍然存在线程安全问题。这是因为在线程执行到第1处的时候,代码读取到instance不为null时,instance引用的对象有可能还没有完成初始化

代码语言:javascript
复制
public class SingletonIns {
    private static SingletonIns instance = null;
    private SingletonIns() {}
    public SingletonIns getInstance() {
        if (null == instance) { //1
            synchronized(SingletonIns.class) {
                if(null == instance)
                instance = new SingletonIns();//2
            }
        }
        return  instance;
    }
}

为什么会出现这样的问题呢?主要的原因是重排序。重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

第2处的代码创建了一个对象,这一行代码可以分解成3个操作:

代码语言:javascript
复制
Copymemory = allocate();  // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory;  // 3:设置instance指向刚分配的内存地址

根源在于代码中的2和3之间,可能会被重排序。例如:

代码语言:javascript
复制
Copymemory = allocate();  // 1:分配对象的内存空间
instance = memory;  // 3:设置instance指向刚分配的内存地址
// 注意,此时对象还没有被初始化!
ctorInstance(memory); // 2:初始化对象java

这在单线程环境下是没有问题的,但在多线程环境下会出现问题:B线程会看到一个还没有被初始化的对象。

A2和A3的重排序不影响线程A的最终结果,但会导致线程B在B1处判断出instance不为空,线程B接下来将访问instance引用的对象。此时,线程B将会访问到一个还未初始化的对象。

重排序能够保证单线程的情况下,执行结果与按顺序执行结果一致,但是无法保证多线程下结果正确。所以我们需要使用**volatile**来修饰**instance**,来禁用重排序,保证线程安全

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-12-18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 要求
  • 实现思路
  • 懒汉式单例模式
  • 饿汉式单例模式
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档