设计模式之单例模式

单例模式是Java中常用的设计模式之一,从系统启动到系统终止,保证一个类仅有一个实例,并提供一个访问它的全局访问点。例如spring中的bean默认都是单例模式。

单例模式的实现方式有多种,一般以下几种

  1. 饿汉式-变量初始化,饿汉式-静态块初始
  2. 懒汉式-同步锁,懒汉式-双重检查锁,懒汉式-内部类延时加载
  3. 登记注册式

饿汉式

饿汉式是保证线程安全的单例模式实现,在类加载的时候就进行单例对象初始化,典型的实现方式如下两种。

饿汉式-变量初始化

单例代码如下

public class HungerSingleton {
    private static HungerSingleton singleton = new HungerSingleton();

    private HungerSingleton(){
    }
    
    public static HungerSingleton getInstance(){
        return singleton;
    }
}

测试代码如下。

public class SingletonTest {
    public static void main(String[] args) {
        for (int i=0;i< 30;i++){
            new Thread(() -> {
                System.out.println(HungerSingleton.getInstance());
            }).start();
        }
    }
}

可以测出多个线程获取的对象都是同一个。

饿汉式-静态块初始化

又或者是使用静态代码块初始化的方式,代码如下。

public class StaticHungerSingleton {
    private static StaticHungerSingleton singleton;
    static {
        singleton = new StaticHungerSingleton();
    }

    private StaticHungerSingleton(){
        
    }
    public static StaticHungerSingleton getInstance(){
        return singleton;
    }
}

使用静态代码块的方式初始化单例值,也是线程安全的。

懒汉式

懒汉式单例会存在线程安全问题,导致单例获取的对象不一定唯一,所以一般需要做同步控制。

懒汉式-同步锁实现

public class SyncLazySingleton {
    private static SyncLazySingleton singleton;

    private SyncLazySingleton(){
    }

    public synchronized static SyncLazySingleton getInstance(){
        if (singleton == null){
            singleton = new SyncLazySingleton();
            return singleton;
        }else {
            return singleton;
        }
    }
}

使用同步关键字,保证不会因为并发导致创建两个不同的对象。使用synchronized关键字实现,可以保证线程安全,但是在并发量大的情况下可能效率收到影响,因此可以做进一步的优化,使用双重检查锁方式优化实现。

懒汉式-双重检查锁实现

public class DubbleCheckLazySingleton {
    private static volatile DubbleCheckLazySingleton singleton;

    private DubbleCheckLazySingleton(){
    }

    public static DubbleCheckLazySingleton getInstance(){
        if (singleton == null){
            synchronized (DubbleCheckLazySingleton.class){
                if (singleton == null){
                    singleton = new DubbleCheckLazySingleton();
                    return singleton;
                }else {
                    return singleton;
                }
            }
        }else {
            return singleton;
        }
    }
}

代码如上所示,实现了更加细粒度的同步控制,因此效率比单独的同步互斥实现要好一点。

懒汉式-内部类延时加载实现

利用内部类延时加载机制以及类加载的安全机制,保证单例在初始时是线程安全的。

代码实现如下。

public class InnerClassLazySingleton {
    private InnerClassLazySingleton(){
    }

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

    private static class SingletonHolder{
        static InnerClassLazySingleton INSTANCE = new InnerClassLazySingleton();
    }
}

上面 的实现方式既保证了懒汉式加载,也保证了线程安全。

登记注册式

登记注册式严格上来说并不是某一个类的单例的实现方式,而是多个类的单例维护的实现方式,例如spring中ioc容器,维护许多单例的bean。提供一个基于Map结构的容器,根据参数获取实例,对于某个参数固定返回一个实例,如果没有实例则进行初始化实例放入容器,然后返回实例。这里同样的,对于懒汉式加载,需要保证线程安全。下面看一个例子。

public class RegisterSingleton {
    private static Map<String, Object> container = new HashMap<>();

    private RegisterSingleton(){
    }

    public static Object getInstance(String type){
        if (Objects.isNull(type)){
            return null;
        }
        Object  o = container.get(type);
        if (Objects.isNull(o)){
            doInitObject(type);
        }
        return container.get(type);
    }

    /**
     * 保证初始化过程线程安全即可
     * @param type
     */
    private synchronized static void doInitObject(String type) {
        if ("set".equals(type)){
            container.put(type, new HashSet<>());
        }else if ("list".equals(type)){
            container.put(type, new ArrayList<>());
        }
    }
}

注册登记式更像是一种综合使用的方式,上面的保证初始化过程线程安全的实现方式可以多种,参考懒汉式加载的方式。最终实现是保证每次传相同的参数获取的对象都是同一个。

注意:序列化和反序列化导致单例不唯一

需要知道,对象实现了序列化接口之后是可以进行序列化的反序列化的,一个单例序列化之后,如果不进行重写readResolve方法,那么在反序列化时就会新建一个对象,导致单例不唯一。

public class SeriableHungerSingleton implements Serializable{
    private static SeriableHungerSingleton singleton = new SeriableHungerSingleton();

    private SeriableHungerSingleton(){
    }

    public static SeriableHungerSingleton getInstance(){
        return singleton;
    }
}

测试代码

public class SeriableTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        SeriableHungerSingleton singleton = SeriableHungerSingleton.getInstance();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("tmp.obj")));
        objectOutputStream.writeObject(singleton);
        objectOutputStream.flush();
        objectOutputStream.close();

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("tmp.obj")));
        SeriableHungerSingleton singleton1 = (SeriableHungerSingleton) objectInputStream.readObject();
        objectInputStream.close();
        System.out.println(singleton);
        System.out.println(singleton1);
    }
}

输出结果

design.mode.singleton.seriable.SeriableHungerSingleton@7f31245a
design.mode.singleton.seriable.SeriableHungerSingleton@6d6f6e28

Process finished with exit code 0

经过重写readResolve方法,代码如下

public class SeriableHungerSingleton implements Serializable{
    private static SeriableHungerSingleton singleton = new SeriableHungerSingleton();

    private SeriableHungerSingleton(){
    }

    public static SeriableHungerSingleton getInstance(){
        return singleton;
    }

    public Object readResolve(){
        return singleton;
    }
}

执行测试,输出结果

design.mode.singleton.seriable.SeriableHungerSingleton@7f31245a
design.mode.singleton.seriable.SeriableHungerSingleton@7f31245a

Process finished with exit code 0

保证了单例模式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值