设计模式——单例模式

1 概述

单例模式就是保证一个类只有一个对象实例。

为了保证无法创建多余的对象实例,单例类中需要自己创建对象实例,并把自己的构造方法私有化以防止其他地方调用创建对象,且需要提供一个公共的方法给其他类来获取该单例类的实例。

同时单例类还可以减少对象的创建与销毁所消耗的时间及性能

2 懒汉式(非线程安全)

java 复制代码
public class SingleObject {
    private static SingleObject instance;

    private SingleObject() {
    }

    public static SingleObject getInstance() {
        if (instance == null) { //1
            instance = new SingleObject();
        }
        return instance;
    }
}

当其他类来调用getInstance方法获取对象实例时,才判断对象是否创建,如果对象没有创建,则创建一个对象并返回,如果对象已经创建,则直接返回。

之所以叫做懒汉式,是因为该方式将对象的初始化工作放到了使用的时候。

这个方式是非线程安全的,因为假设线程a执行到注释1处判断对象是否等于null,此时为true,那么会继续执行new对象的操作,但在这个操作之前,发生了线程切换,线程b也会判断对象为null,然后new一个对象,当线程切换回线程a时,线程a也会new一个对象,从而导致创建了两个对象,两个线程获得的也是两个不同的SingleObject对象。

3 懒汉式(线程安全)

java 复制代码
public class SingleObject {
    private static SingleObject instance;

    private SingleObject() {
    }

    public static synchronized SingleObject getInstance() {
        if (instance == null) {
            instance = new SingleObject();
        }
        return instance;
    }
}

在上面的基础上加上synchronized 关键字,使之成为同步方法。但同步锁是一个重量级的锁,每次获取单例的时候都加锁会带来性能开销。

4 恶汉式(线程安全)

java 复制代码
public class SingleObject {
    private static final SingleObject INSTANCE = new SingleObject();

    private SingleObject() {
    }

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

类初始化时就创建单例对象,由于调用中没有new操作,所以无法操作该单例对象,所以线程安全。

改方式的缺点就是类初始化的时候就创建对象,在使用前该对象一直占用着内存,会形成内存的无效占用。

5 双重校验锁(线程安全)

java 复制代码
public class SingleObject {
    private volatile static SingleObject instance;

    private SingleObject() {
    }

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

instance使用volatile修饰,保证可见性和禁止指令重排。

双重检测instance是否为null,第一层检测如果不为null,则直接返回,避免了懒汉式(线程安全)的每次获取实例都需要加锁的消耗。如果为null,则加锁创建对象实例。

为什么要使用volatile关键字修饰instance

因为如果不使用volatile,多线程时在虚拟机优化------指令重排的情况下,可能会导致线程获取的实例没有初始化。

new和赋值操作在JVM的指令中时4个指令,第一条是类的实例创建指令,会返回一个引用到操作数栈的栈顶,第二条指令是将栈顶复制一份并存入栈顶,第三条指令是调用构造方法,第四条指令则是给静态字段赋值。

第三条和第四条指令是可能出现指令重排的,如果第四条指令先执行,则此时instance就不为null了,有了指向对象的指令。但是由于第三条指令没有执行,改对象却没有初始化。如果出现指令重排,就可能出现有线程获取到没有初始化的对象并使用该对象进行一些操作。

6 静态内部类(线程安全)

java 复制代码
public class SingleObject {
    private static class SingleInner {
        private static final SingleObject INSTANCE = new SingleObject();
    }

    private SingleObject() {
    }

    public static SingleObject getInstance() {
        return SingleInner.INSTANCE;
    }
}

由于内部类声明为private的,在SingleObject外无法访问,所以除了内部类中创建的对象,无法在其他地方创建对象。

这种方式也是延迟初始化的,因为只有调用getInstance方法时,才会导致SingleInner类加载初始化,并创建对象。

7 枚举(线程安全)

java 复制代码
public enum SingleObject {
    INSTANCE;

    public void testMethod() {
        System.out.println("test");
    }
}

利用枚举类的特性,只定义一个枚举类对象,那么这个枚举类就自然的是单例类了。

同时通过枚举类实现还有以下好处:自动支持序列化,能够防止反序列化时创建对象。

该方式是最推荐的方式

相关推荐
chuanauc2 分钟前
Kubernets K8s 学习
java·学习·kubernetes
一头生产的驴18 分钟前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao25 分钟前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc78728 分钟前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展
程序员张33 小时前
SpringBoot计时一次请求耗时
java·spring boot·后端
llwszx6 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁
Small black human6 小时前
设计模式-应用分层
设计模式
云泽野6 小时前
【Java|集合类】list遍历的6种方式
java·python·list
二进制person7 小时前
Java SE--方法的使用
java·开发语言·算法