设计模式——单例模式

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

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

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

该方式是最推荐的方式

相关推荐
信徒_6 分钟前
常用设计模式
java·单例模式·设计模式
神仙别闹11 分钟前
基于C#实现的(WinForm)模拟操作系统文件管理系统
java·git·ffmpeg
小爬虫程序猿12 分钟前
利用Java爬虫速卖通按关键字搜索AliExpress商品
java·开发语言·爬虫
组合缺一17 分钟前
Solon v3.0.5 发布!(Spring 可以退休了吗?)
java·后端·spring·solon
程序猿零零漆20 分钟前
SpringCloud 系列教程:微服务的未来(二)Mybatis-Plus的条件构造器、自定义SQL、Service接口基本用法
java·spring cloud·mybatis-plus
猿来入此小猿21 分钟前
基于SpringBoot在线音乐系统平台功能实现十二
java·spring boot·后端·毕业设计·音乐系统·音乐平台·毕业源码
愤怒的代码35 分钟前
Spring Boot对访问密钥加解密——HMAC-SHA256
java·spring boot·后端
带多刺的玫瑰36 分钟前
Leecode刷题C语言之切蛋糕的最小总开销①
java·数据结构·算法
栗豆包1 小时前
w118共享汽车管理系统
java·spring boot·后端·spring·tomcat·maven
夜半被帅醒1 小时前
MySQL 数据库优化详解【Java数据库调优】
java·数据库·mysql