单例模式总结

165 阅读4分钟

1.简介

单例模式是软件开发中常用的一种设计模式,它要确保某个类在系统中只有唯一的一个实例

2.应用场景

a.共享资源,保证数据的一致性,如配置文件
b.创建对象耗时或者消耗资源过多,但又经常使用到,如数据库连接池、线程池等
c.工具类对象

3.实现方式

a.饿汉式

public class SingletonOne {

    private static final SingletonOne singletonOne = new SingletonOne();

    private SingletonOne() {
        if (singletonOne != null) {
            // 防止反射获取多个对象的漏洞
            throw new RuntimeException("instance has init");
        }
    }

    public static SingletonOne getInstance() {
        return singletonOne;
    }

    // 防止反序列化获取多个对象的漏洞
    private Object readResolve() throws ObjectStreamException {
        return singletonOne;
    }
}

优点:

类加载时完成对象实例化,避免多线程下的同步问题,同时获取单例速度较快

缺点:

资源利用效率不高,单例可能一直用不到,但是已经初始化了,造成资源浪费

b.懒汉式

public class SingletonTwo {

    private static SingletonTwo singletonTwo = null;

    private SingletonTwo() {
        if (singletonTwo != null) {
            throw new RuntimeException("instance has init");
        }
    }

    public static SingletonTwo getInstance(){
        if (singletonTwo == null) {
            singletonTwo = new SingletonTwo();
        }
        return singletonTwo;
    }

    // 防止反序列化获取多个对象的漏洞
    private Object readResolve() throws ObjectStreamException {
        return singletonTwo;
    }
}

优点:

懒加载,第一次调用时实例化对象,资源利用率高,后续调用速度快

缺点:

多线程场景下,会产生多个对象

c.懒汉式+锁

public class SingletonThree {

    private static SingletonThree singletonThree = null;

    private SingletonThree() {
        if (singletonThree != null) {
            throw new RuntimeException("instance has init");
        }
    }

    public static SingletonThree getInstance() {
        synchronized (SingletonThree.class) {
            if (singletonThree == null) {
                singletonThree = new SingletonThree();
            }
        }
        return singletonThree;
    }

    // 防止反序列化获取多个对象的漏洞
    private Object readResolve() throws ObjectStreamException {
        return singletonThree;
    }
}

优点:

通过加锁解决多线程场景下产生多个实例的问题

缺点:

加锁后,每次调用方法都需要获取锁,影响性能,其实只需要在第一次实例化对象时调用对象

d.双重检查锁(DCL)

public class SingletonFour {

    private static SingletonFour singletonFour = null;

    private SingletonFour() {
        if (singletonFour != null) {
            throw new RuntimeException("instance has init");
        }
    }

    public static SingletonFour getInstance() {
        if (singletonFour == null) {
            synchronized (SingletonFour.class) {
                if (singletonFour == null) {
                    singletonFour = new SingletonFour();
                }
            }
        }
        return singletonFour;
    }

    // 防止反序列化获取多个对象的漏洞
    private Object readResolve() throws ObjectStreamException {
        return singletonFour;
    }
}

优点:

未实例化时才会去竞争锁来实例化,实例化后直接返回实例对象,提升性能同时解决多线程的同步问题

缺点:

由于jvm的指令重排问题,在多线程场景下会引用一个未初始化完成的对象,导致系统出现问题,具体原因如下:对象创建时,其实是分三步完成的,1)在堆上给对象分配内存,2)调用构造方法初始化此对象,3)将对象引用指向对应内存地址,由于jvm的指令重排机制,这三步可能不是顺序执行的,如果是1)3)2)顺序,3)完成2)还没完成时,另一个线程判断对象不为null会直接返回未初始化完全的对象,导致系统出现问题

e.双重检查锁(优化)

public class SingletonFour {

    private static volatile SingletonFour singletonFour = null;

    private SingletonFour() {
        if (singletonFour != null) {
            throw new RuntimeException("instance has init");
        }
    }

    public static SingletonFour getInstance() {
        if (singletonFour == null) {
            synchronized (SingletonFour.class) {
                if (singletonFour == null) {
                    singletonFour = new SingletonFour();
                }
            }
        }
        return singletonFour;
    }

    // 防止反序列化获取多个对象的漏洞
    private Object readResolve() throws ObjectStreamException {
        return singletonFour;
    }
}

优点:

在双重检查锁的基础增加了volatile关键字,禁止指令重排,避免出现引用未初始化完成的对象

缺点:

禁用指令重排后,jvm的一些代码优化措施会无效,影响性能

f.内部静态类

public class SingletonFive {

    private SingletonFive() {
        // 防止反射获取多个对象的漏洞
        if (Singleton.singleton != null) {
            throw new RuntimeException("instance has init");
        }
    }

    private static class Singleton {
        private static final SingletonFive singleton = new SingletonFive();
    }

    public static SingletonFive getInstance() {
        return Singleton.singleton;
    }

    // 防止反序列化获取多个对象的漏洞
    private Object readResolve() throws ObjectStreamException {
        return Singleton.singleton;
    }
}

推荐使用

懒加载,线程安全

内部类不会随着外部类初始化,只会在调用getInstance时才会初始化,而由于类的初始化<clinit>()会被加锁,所以也是线程安全的

g.枚举

public enum SingletonSix {

    singleton;
  
}

推荐使用

只需调用SingletonSix. singleton,写法简单且线程安全,还能防止反序列化重新创建对象,只会存在一个实例

4.结语

单例模式实现方式常见有以上这几种,推荐使用内部静态类和枚举的方式实现单例。同时在使用过程中要注意避免反射和反序列化创建多个对象