设计模式——单例模式

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");
    }
}

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

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

该方式是最推荐的方式

相关推荐
bagadesu1 小时前
使用Docker构建Node.js应用的详细指南
java·后端
没有bug.的程序员1 小时前
Spring Cloud Gateway 性能优化与限流设计
java·spring boot·spring·nacos·性能优化·gateway·springcloud
洛_尘2 小时前
JAVA EE初阶 2: 多线程-初阶
java·开发语言
Slow菜鸟2 小时前
Java 开发环境安装指南(五) | Git 安装
java·git
lkbhua莱克瓦243 小时前
Java基础——方法
java·开发语言·笔记·github·学习方法
q***71854 小时前
海康威视摄像头ISUP(原EHOME协议) 摄像头实时预览springboot 版本java实现,并可以在浏览器vue前端播放(附带源码)
java·前端·spring boot
_Jimmy_4 小时前
JUC包里的同步组件主要实现了AQS的哪些主要方法
java
范纹杉想快点毕业4 小时前
《嵌入式开发硬核指南:91问一次讲透底层到架构》
java·开发语言·数据库·单片机·嵌入式硬件·mongodb
大G的笔记本4 小时前
Java常见设计模式面试题(高频)
java·开发语言·设计模式
老鼠只爱大米5 小时前
Java设计模式之建造者模式(Builder)详解
java·设计模式·建造者模式·builder·23种设计模式