Java单例模式

昨天读到了公众号“Import New”的Hi,我们再来聊一聊 Java 的单例 很有收获,在此做个简单的记录。

1.懒汉式

1.1简单式

// Version 1
public class Single1 {
    private static Single1 instance;
    public static Single1 getInstance() {
        if (instance == null) {
            instance = new Single1();
        }
        return instance;
    }
}
// Version 1.1
public class Single1 {
    private static Single1 instance;
    private Single1() {}
    public static Single1 getInstance() {
        if (instance == null) {
            instance = new Single1();
        }
        return instance;
    }
}

问题:多个线程同时访问,如果有多个线程同时运行到if (instance == null)时,都判断为null,这个时候就不是单例了。
想法:加上synchronized同步锁

1.2synchronized版本

// Version 2 
public class Single2 {
    private static Single2 instance;
    private Single2() {}
    public static synchronized Single2 getInstance() {
        if (instance == null) {
            instance = new Single2();
        }
        return instance;
    }
}

问题:给gitInstance方法加锁,虽然会避免了可能会出现的多个实例问题,但是会强制除T1之外的所有线程等待,实际上会对程序的执行效率造成负面影响。
想法:double-check

1.3 double-chek版本

// Version 3 
public class Single3 {
    private static Single3 instance;
    private Single3() {}
    public static Single3 getInstance() {
        if (instance == null) {
            synchronized (Single3.class) {
                if (instance == null) {
                    instance = new Single3();
                }
            }
        }
        return instance;
    }
}

第一个if (instance == null)是为了解决上一方案中的效率问题
第二个if (instance == null)是为了防止多个实例
问题:1、instance = new Single3();非原子操作 2、会受到指令重排的影响。

原子操作:简单来说,原子操作(atomic)就是不可分割的操作,在计算机中,就是指不会因为线程调度被打断的操作。
例如:赋值操作

m = 6;

指令重排:简单来说,就是计算机为了提高执行效率,会做的一些优化,在不影响最终结果的情况下,可能会对一些语句的执行顺序进行调整。

下面这段话直接从陈皓的文章(深入浅出单实例SINGLETON设计模式)中复制而来:

主要在于singleton = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

  1. 给 singleton 分配内存

  2. 调用 Singleton 的构造函数来初始化成员变量,形成实例

  3. 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null 了)
    但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

再稍微解释一下,就是说,由于有一个『instance已经不为null但是仍没有完成初始化』的中间状态,而这个时候,如果有其他线程刚好运行到第一层if (instance == null)这里,这里读取到的instance已经不为null了,所以就直接把这个中间状态的instance拿去用了,就会产生问题。

这里的关键在于——线程T1对instance的写操作没有完成,线程T2就执行了读操作。

1.4 终极版本:volatile

// Version 4 
public class Single4 {
    private static volatile Single4 instance;
    private Single4() {}
    public static Single4 getInstance() {
        if (instance == null) {
            synchronized (Single4.class) {
                if (instance == null) {
                    instance = new Single4();
                }
            }
        }
        return instance;
    }
}

volatile关键字的一个作用是禁止指令重排,把instance声明为volatile之后,对它的写操作就会有一个内存屏障(什么是内存屏障?),这样,在它的赋值完成之前,就不用会调用读操作。

注意:volatile阻止的不是singleton = new Singleton()这句话内部[1-2-3]的指令重排,而是保证了在一个写操作([1-2-3])完成之前,不会调用读操作(if (instance == null))。

2.饿汉式单例

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

2.1 饿汉式单例的实现方式

//饿汉式实现
public class SingleB {
    private static final SingleB INSTANCE = new SingleB();
    private SingleB() {}
    public static SingleB getInstance() {
        return INSTANCE;
    }
}

问题:INSTANCE的初始化是在类加载时进行的,而类的加载是由ClassLoader来做的,所以开发者本来对于它初始化的时机就很难去准确把握。

3 其他的一些方式

3.1 Effective Java 1 —— 静态内部类

// Effective Java 第一版推荐写法
public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){}
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

3.2 Effective Java 2 —— 枚举

// Effective Java 第二版推荐写法
public enum SingleInstance {
    INSTANCE;
    public void fun1() { 
        // do something
    }
}
 
// 使用
SingleInstance.INSTANCE.fun1();

4.可参考的链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 主要参考自 菜鸟教程 单例模式是JAVA中最简单的模式之一,这种模式属于创建型模式,它提供了一种创建对象的最...
    东溪95阅读 570评论 0 2
  • 单例的定义 确保某一个类只有一个实例,而且自行实例化并且向整个系统提供整个实例。 使用场景 确保一个类有且只有一个...
    Tyhj阅读 153评论 0 0
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,370评论 11 349
  • 1. 实现单例模式 饿汉模式和懒汉模式单例模式根据实例化时机分为饿汉模式和懒汉模式。饿汉模式,是指不等到单例真正使...
    aaron1993阅读 228评论 0 0
  • 今天是5.20,有些人为了今天准备了很久,有些小清新的约会,又或是浪漫的告白。女孩,原谅我在这里用这个你看不见的方...
    阿锦男孩阅读 524评论 0 0