【设计模式-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。

总结

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

相关推荐
百事老饼干14 分钟前
Java[面试题]-真实面试
java·开发语言·面试
customer0822 分钟前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea
2402_8575893632 分钟前
SpringBoot框架:作业管理技术新解
java·spring boot·后端
HBryce2436 分钟前
缓存-基础概念
java·缓存
一只爱打拳的程序猿1 小时前
【Spring】更加简单的将对象存入Spring中并使用
java·后端·spring
杨荧1 小时前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
minDuck1 小时前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
为将者,自当识天晓地。1 小时前
c++多线程
java·开发语言
daqinzl1 小时前
java获取机器ip、mac
java·mac·ip