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

设计之禅——单例模式详解

作者头像
夜勿语
发布2020-09-07 10:40:32
3620
发布2020-09-07 10:40:32
举报
文章被收录于专栏:Java升级之路

一、前言

有时候我们只需要一个类只有一个对象,如,线程池、缓存、windows的任务管理器、注册表等,因此就有了单例模式,确保了一个类只存在一个实例。单例模式的实现非常简单,但是其中的细节也需要注意。下面我们就来看看他的各种实现。

二、实现

单例模式的实现方式有很多,根据是否立即创建对象分为“懒汉”和“饿汉”两大类别,即是否在类加载时立即创建对象,如果该对象频繁被使用,可以使用“饿汉式”提高效率;反之则可以使用“懒汉式”来避免内存的浪费。而“懒汉式”的创建在多线程环境下则有许多方式来保证线程安全。

1. 懒汉式-线程不安全

代码语言:javascript
复制
public class Singleton {
	
    public static Singleton instance;
    
 	// 私有化构造方法,保证外部无法创建对象
    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }

        return instance;
    }

}

这种只能保证在单线下获取到单例对象,并且在需要的时候才会创建对象,故此称为“懒汉式”。但是因为new Singleton()该操作并不是原子操作,当线程1执行到此时,可能还并未创建实例,那么线程2在判断instance==null时就会为真,从而产生多个实例。

2. 懒汉式-线程安全

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

    private static Singleton instance;

	// 私有化构造方法,保证外部无法创建对象
    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }

        return instance;
    }

}

这种方式保证了线程安全,但是效率非常低,因此一般不推荐使用。

3. 饿汉式

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

    private static Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }

}

与懒汉式的区别是类加载的时候立即创建了对象实例,保证了对象始终只会有一个,但是如果该对象一直不被使用,就会浪费内存资源。

4. 静态内部类

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

    private Singleton() {}

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }

}

使用静态内部类来实现单例其实也是懒汉式的优化实现,利用类初始化时线程安全这一特点来创建单例对象,同时因为是在静态内部类中,有且仅当getInstance()方法被调用时才会被初始化,所以也避免了内存的浪费。

5. 双重校验锁

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

    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }

}

该方式是“饿汉式”的变种,保留了“饿汉式”的特点的同时保证了线程的安全。但是,需要注意的是volatile关键字是必须的,在网上很多文章上看到都没带这个关键字,如果不加可能会导致程序的崩溃。因此该方法只能在JDK1.5后使用。 volatile是保证线程之间的可见性。倘若没有该关键字,假设线程1和线程2先后调用getInstance()方法,当线程1进入方法时判断instance=null,因此去执行new Singleton()创建实例,上文提到该操作并非原子操作,会被编译为三条指令:

  1. 分配对象的内存空间;
  2. 初始化对象;
  3. 将对象指向内存地址;

而jvm会为了执行效率而进行指令重排,重排后的指令顺序为:1->3->2,当指令执行完第3条指令,此时线程2进入方法进行第一次判断时,就会得到一个并不完整的对象实例(因为对象还未初始化,只是分配了内存空间),接着线程1执行完第2条指令,又会返回这个实例的完全态,但并不会立即刷新主内存,所以线程2并不能访问到,程序就会出现错误导致崩溃。而volatile就是为了处理这个问题,他能保证当某个线程改变对象实例后,立即刷新主内存,让其他线程能够同样获取到相同的实例对象,就不会出现不一致的问题了。

6. 枚举

代码语言:javascript
复制
public enum Singleton {
    INSTANCE;
}

用枚举的方式创建单例非常简单明了,它本身能保证线程的安全,还能防止反序列化(readObject())导致对象不一致的问题,唯一的缺点则是同饿汉式一样会立即创建对象实例(反编译后可以看到),如果不考虑这点枚举应是单例实现的最佳方式,也是《Effective Java》作者推荐的方式。

三、总结

单例模式是比较常用的模式之一,本文总结了6种实现方式,可以感受到看似简单的代码背后涉及到的细节非常多,因此也是非常考验我们的基本功。在本文中并没有考虑反射入侵的情况,有兴趣的读者们可自行研究。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、实现
    • 1. 懒汉式-线程不安全
      • 2. 懒汉式-线程安全
        • 3. 饿汉式
          • 4. 静态内部类
            • 5. 双重校验锁
              • 6. 枚举
              • 三、总结
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档