单例模式的实现

Singleton 单例,表示仅仅被实例化一次的类。将构造器私有化是实现 Singleton 的最常见方法,将类的构造器私有化,在类的内部实例化一个对象,将对象的域公开发布出去,从而保证了对象的全局唯一性。基于此,实现单例模式的几种方式如下:

一、类加载时就进行唯一实例的初始化,即所谓饿汉式

此种情况由于对象在加载类时即被初始化,因此可以使用以下两种方式将对象暴露给类外

① 静态 final 域 Singleton.INSTANCE 是公有的

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

② 静态 final 域 Singleton.INSTANCE 是私有的,通过公有静态方法 getInstance() 发布。

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

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

二、懒加载,即所谓懒汉式

当单例对象的成员较多,占用内存较大时,或给初始化启动带来较大压力,因此将单例对象的构造推迟至第一次使用该对象时,简单版本如下:

public class Singleton {
    private static final Singleton INSTANCE;
    private Singleton () {}
    
    public static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}

上述方法只有在调用 getInstance() 方法时,才会进行单例的初始化构造,并通过空值判断保证单一实例。

但是在多线程时,由于并发问题导致可能不同线程返回不同实例,因此需要做并发改善

public class Singleton {
    private static final Singleton INSTANCE;
    private Singleton () {}
    
    public synchronized static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}public class Singleton {
    private static final Singleton INSTANCE;
    private Singleton () {}
    
    public static Singleton getInstance() {
        synchronized(Singleton.class) {
            if (INSTANCE == null) {
                INSTANCE = new Singleton();
            }
            return INSTANCE;
        }
    }
}

上述通过 synchronized 关键字做了同步处理,但线程每次访问获取实例时都需要竞争锁,导致获取实例的过程实际变成了串行,但其实并发问题只存在于第一次进行实例的初始化过程,因此做如下改进

public class Singleton {
    // volatile 关键字禁止指令重排序,防止出现空指针异常
    // 异常产生原因:
    // 由于 new 关键字在 JVM 中并不是原子操作,可分为三步
    // 1 分配内存
    // 2 构造初始化
    // 3 引用赋值
    // 在指向操作时,可通过指令重排序进行优化,2 和 3 进行重排
    // 当某线程执行完 1 时,指令重排先执行了 3,时间片用完,线程切换
    // 此时若发生并发,另一线程在判断 INSTANCE == null 时为 false,返回了
    // 还未执行构造的 INSTANCE 引用,后续对 INSTANCE 对象的成员进行访问时,
    // 可能会出现空值与空指针的问题
    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;
    }
}

三、静态内部类

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

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

通过使用静态内部类的特性,作为类的成员,只有当调用 getInstance() 方法时,才会触发 SingletonHolder 内部类的类加载过程,进而确保了线程安全与懒加载。

注意:到此,上述的方法中都存在两个隐含问题导致有可能破坏单例

  • 通过反射强制调用构造方法
  • 由于对象序列化与反序列化导致的单例模式破坏

解决方案:

对于反射,可以在私有构造器中抛出异常避免。

private static volatile boolean flag = true;

private Singleton() {
    if (flag ) {
        flag = false;
    } else {
        throw new RuntimeException("重复对象实例化错误");
    }
}

补充:对于一些永远不需要实例化的工具类,可以直接使用

private Singleton() {
    throw new RuntimeException("重复对象实例化错误");
}

对于序列化问题,需要在类中添加如下方法:

private Object readResolve() throws ObjectStreamException {
    return INSTANCE;
}

四、枚举

枚举方式更加简洁、并无偿提供了序列化机制,使用单元素的枚举类型经常成为实现 Singleton 的最佳方法

public enum Singleton { 
	INSTANCE;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值