单例模式 - 最强王者

本文详细介绍了Java中单例模式的实现方式,包括饿汉式、懒汉式及其多线程安全的改进版,以及静态内部类和枚举方式。分析了各种实现的优缺点,如内存占用、线程安全和延迟加载,并讨论了在不同环境下的适用性,强调了单例模式的三大要点:线程安全、延迟加载和序列化安全。
摘要由CSDN通过智能技术生成

饿汉式

饿了就要吃,所以我不能等待了,你要立刻给我一个对象,所以饿汉式是在类加载时就创建对象(方便记忆)。

public class Singleton {
    private static final Singleton = new Singleton();
    private Singleton() {}
    public static getSignleton(){
        return singleton;
    }
}

饿汉式优点:

  1. 代码简洁;
  2. 暂时没遇到需要考虑多线程安全的问题。
饿汉式缺点:

1.无法做到延迟创建对象,不使用该单例时,你还创建一个对象,不是白白浪费内存吗?

懒汉式

哎呀,你暂时不使用,我就不创建了,你用的时候,我再给创建一个对象。这个就是懒汉式了,你不用我不创建对象,非要等到你使用我再创建对象,一个字:懒(方便记忆)。

单线程写法:懒汉式之青铜级别

public class Singleton {
    private static Singleton singleton = null;
    private Singleton(){}
    public static Singleton getSingleton() {
        if(singleton == null) singleton = new Singleton();
        return singleton;
    }
}

由私有构造器和一个公有静态工厂方法构成,在工厂方法中对 singleton 进行 null 判断,如果是 null 就 new 一个出来,最后返回 singleton 对象。这种方法可以实现延时加载

优点:
  1. 做到了延迟创建对象,不使用该单例时,不创建对象。
缺点:
  1. 遇到多线程项目,鬼知道会给你创建出多少个对象来。

考虑线程安全的写法:懒汉式之白银级别

public class Singleton {
    private static volatile Singleton singleton = null;
    private Singleton(){}
    public static Singleton getSingleton(){
        synchronized (Singleton.class){
            if(singleton == null){
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}

volatile关键字可以保证了线程每次拿到的singleton对象是最新的(详解可见参考网站)。在 getSingleton() 方法中,对singleton的判空和创建对象,做了一个加锁(synchronized),看似完美实则有坑,欲知详情,客官接着看便是。

优点:
  1. 做到了延迟创建对象:不使用该单例时,不创建对象。
  2. 考虑到了多线程安全问题。
  3. singleton 对象使用 volatile 关键字进行限制,保证其对所有线程的可见性,并且禁止对其进行指令重排序优化。
缺点:
  1. 效率低下:因为每次调用 getSingleton() 方法,都必须在 synchronized 这里进行排队,而真正遇到需要 new 的情况是非常少的。

考虑线程安全的写法:懒汉式之黄金级别

在“锁”外面再添加一层判断

public class Singleton {
    private static volatile Singleton singleton = null;
    private Singleton(){}
    public static Singleton getSingleton(){
        if(singleton == null){
            synchronized (Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
优点:
  1. 提高并发度:每次调用 getSingleton() 前先判断,而不是让线程先去和锁打交道。既然是单例,那么加锁部分肯定要做到只执行一次,所以只进入锁一次,只创建对象一次。后续调用 getSingleton() 时,锁都被第一次判空给拦截了。
缺点

1.volatile 关键字有坑

volatile的作用一:volatile关键字可以保证了线程每次拿到的singleton对象是最新的
在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以会马上反应在其它线程的读取操作中。工作内存和主内存可以近似理解为实际电脑中的高速缓存和主存,工作内存是线程独享的,主存是线程共享的。volatile 的第二层语义是禁止指令重排序优化。大家知道我们写的代码(尤其是多线程代码),由于编译器优化,在实际执行的时候可能与我们编写的顺序不同。
volatile的作用二:禁止指令重排序优化,有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置)。但是很不幸,禁止指令重排优化这条语义直到 jdk1.5 以后才能正确工作。此前的 JDK 中即使将变量声明为 volatile 也无法完全避免重排序所导致的问题。所以,在 jdk1.5 版本前,双重检查锁形式的单例模式是无法保证线程安全的。

静态内部类法:懒汉式之铂金级别

public class Singleton {
    private static class Holder {
        private static Singleton singleton = new Singleton();
    }
    private Singleton(){}
    public static Singleton getSingleton(){
        return Holder.singleton;
    }
}
优点:
  1. 没有延时加载;
  2. 证线程安全:静态内部类只会被加载一次。

上面提到的所有实现方式都有两个共同的缺点

  1. 都需要额外的工作 (Serializable、transient、readResolve()) 来实现序列化,否则每次反序列化一个序列化的对象实例时都会创建一个新的实例。
  2. 可能会有人使用反射强行调用我们的私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。枚举写法 当然,还有一种更加优雅的方法来实现单例模式,那就是枚举写法。

枚举写法:懒汉式之王者级别(跳级了)

public enum Singleton {
    INSTANCE;
    private String name;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
}

使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,Effective Java 推荐尽可能地使用枚举来实现单例。

总结

代码没有一劳永逸的写法,只有在特定条件下最合适的写法。在不同的平台、不同的开发环境(尤其是 jdk 版本)下,自然有不同的最优解(或者说较优解)。比如枚举,虽然 Effective Java 中推荐使用,但是在 Android 平台上却是不被推荐的。在这篇 Android Training 中明确指出:

Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.

再比如双重检查锁法,不能在 jdk1.5 之前使用,而在 Android 平台上使用就比较放心了(一般 Android 都是 jdk1.6 以上了,不仅修正了 volatile 的语义问题,还加入了不少锁优化,使得多线程同步的开销降低不少)。

如果不考虑延迟加载问题,个人认为直接用饿汉式就好。

最后,不管采取何种方案,请时刻牢记单例的三大要点:

  1. 线程安全
  2. 延迟加载
  3. 序列化与反序列化安全

参考网站

  1. Volatile关键字详解参考:https://www.cnblogs.com/zhengbin/p/5654805.html

备注:本文如有任何内容问题,请各位客官及时指正,要改要删,亲亲客官说了算。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值