设计模式——单例模式

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

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

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

该方式是最推荐的方式

相关推荐
千千寰宇1 小时前
[设计模式/Java/多线程] 设计模式之单例模式【9】
设计模式·操作系统-进程/线程/并发
咖啡教室5 小时前
java日常开发笔记和开发问题记录
java
咖啡教室5 小时前
java练习项目记录笔记
java
鱼樱前端5 小时前
maven的基础安装和使用--mac/window版本
java·后端
RainbowSea6 小时前
6. RabbitMQ 死信队列的详细操作编写
java·消息队列·rabbitmq
RainbowSea6 小时前
5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明
java·消息队列·rabbitmq
李少兄8 小时前
Unirest:优雅的Java HTTP客户端库
java·开发语言·http
此木|西贝8 小时前
【设计模式】原型模式
java·设计模式·原型模式
可乐加.糖8 小时前
一篇关于Netty相关的梳理总结
java·后端·网络协议·netty·信息与通信
s9123601018 小时前
rust 同时处理多个异步任务
java·数据库·rust