【设计模式-2.1】创建型——单例模式

说明:设计模式根据用途分为创建型、结构性和行为型。创建型模式主要用于描述如何创建对象,本文介绍创建型中的单例模式。

饿汉式单例

单例模式是比较常见的一种设计模式,旨在确保对象的唯一性,什么时候去使用这个对象都是同一个。这样设计的目的是为了避免对象重复创建,浪费资源,同时也保证了对象的唯一性,不至于多个相同的对象,状态不一致的情况。

以下是单例模式的简单实现:

java 复制代码
/**
 * 太阳类
 */
public class Sun {
    
    private static final Sun sun = new Sun();

    private Sun() {
    }

    public static Sun getInstance() {
        return sun;
    }
}

需要注意,对象的创建是在对象类内部,且为static final修饰,并且私有化构造方法,使其不能在外部被创建,其次创建一个静态方法,返回其对象;

此时,外部通过类名.方法名的方式,可以访问到该对象,且始终为同一个;

java 复制代码
public class Test {
    public static void main(String[] args) {
        Sun sun1 = Sun.getInstance();
        Sun sun2 = Sun.getInstance();
        System.out.println(sun1 == sun2);
    }
}

两次获取到的对象相同

上面这种创建方式,称为饿汉式单例(Eager Singleton),即类加载完成时就创建对象;

懒汉式单例

以下这种方式,称为懒汉式单例(Lazy Singleton),在调用静态方法时才创建对象;

java 复制代码
/**
 * 懒汉式单例模式
 */
public class LazySun {
    
    private static LazySun sun = null;

    private LazySun() {
    }

    public static LazySun getInstance() {
        if (sun == null) {
            sun = new LazySun();
        }
        return sun;
    }
}

这种创建方式,可以实现延迟加载,节约资源。但是在多线程并发时,如果有多个线程在if (sun == null),就会导致对象被创建多次,所以我们很容易想到的是加锁,将静态方法加上锁,如下:

java 复制代码
/**
 * 懒汉式单例模式
 */
public class LazySun {

    private static LazySun sun = null;

    private LazySun() {
    }

    public synchronized static LazySun getInstance() {
        if (sun == null) {
            sun = new LazySun();
        }
        return sun;
    }
}

但是,给方法加上锁后,会影响性能,可以考虑减少锁的范围,只锁住创建对象这一行,如下:

java 复制代码
/**
 * 懒汉式单例模式
 */
public class LazySun {

    private static LazySun sun = null;

    private LazySun() {
    }

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

双重检查锁定

但是,还有问题。在多线程并发下,如果线程A在创建对象(未完成),线程B、C在if (sun == null) 这里判断为true,即便对象创建完成,线程B、C通过了if判断,也还是会依次再创建对象,也造成了对象被重复创建。因此,还需要改造,如下:

java 复制代码
/**
 * 双重检查锁定
 */
public class LazySun {

    private volatile static LazySun sun = null;

    private LazySun() {
    }

    public static LazySun getInstance() {
        // 第一次判断
        if (sun == null) {
            synchronized (LazySun.class) {
                // 第二次判断
                if (sun == null){
                    sun = new LazySun();
                }
            }
        }
        return sun;
    }
}

就是在锁住的代码块里面再进行一次非空判断,称为双重检查锁定(Double-Check Locking),同时,单例对象修饰符加上volatile,确保多个线程都能正确处理;

IoDH

饿汉式单例,是在类被加载时就创建了对象。不需要考虑多线程,但是无论是否使用到对象都创建,造成了资源浪费。而懒汉式单例,虽然做到了延迟加载,但是需要处理好多线程情况下的对象创建,使用了锁,影响了性能。

那有没有一种更好了方式,既能在多线程下使用,又不会影响性能?

在《设计模式的艺术》(刘伟著)中作者提供了一种更好的创建方式,称为IoDH(Initialization on Demand Holder,按需初始化),代码如下:

java 复制代码
/**
 * IoDH
 */
public class Singleton {

    private Singleton() {
    }

    private static class HolderClass {
        private final static Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return HolderClass.instance;
    }
}

这种方式是对饿汉式单例的改进,是在单例类里创建了一个内部类,将单例对象的创建放在了这个内部类里面。因为单例对象不是单例类的一个成员变量,所以对象在类加载时不会被创建,而是会在调用静态方法时被创建,这样既能延迟加载,又没有使用锁,影响性能,一举两得。

书中说,通过使用IoDH,既可以实现延迟加载,又可以保证线程安全,不影响系统性能。因此,IoDH不失为一种最好的Java语言单例模式实现方式;其缺点是与编程语言本身的特性相关,很多面向对象语言不支持IoDH。

总结

本文参考《设计模式的艺术》、《秒懂设计模式》两书

相关推荐
seabirdssss20 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续20 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben04420 小时前
ReAct模式解读
java·ai
烛阴20 小时前
【TS 设计模式完全指南】从“入门”到“劝退”,彻底搞懂单例模式
javascript·设计模式·typescript
轮到我狗叫了20 小时前
牛客.小红的子串牛客.kotori和抽卡牛客.循环汉诺塔牛客.ruby和薯条
java·开发语言·算法
狂奔的sherry21 小时前
单例模式(巨通俗易懂)普通单例,懒汉单例的实现和区别,依赖注入......
开发语言·c++·单例模式
Volunteer Technology1 天前
三高项目-缓存设计
java·spring·缓存·高并发·高可用·高数据量
栗子~~1 天前
bat脚本- 将jar 包批量安装到 Maven 本地仓库
java·maven·jar
Meteors.1 天前
23种设计模式——原型模式 (Prototype Pattern)详解
设计模式·原型模式
Mr.Entropy1 天前
ecplise配置maven插件
java·maven