设计模式——单例模式

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

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

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

该方式是最推荐的方式

相关推荐
羊锦磊2 小时前
[ Mybatis 多表关联查询 ] resultMap
java·开发语言·数据库·mysql·mybatis
ZeroToOneDev5 小时前
Java(泛型和JUnit)
java·开发语言·笔记
迪尔~6 小时前
Apache POI中通过WorkBook写入图片后出现导出PDF文件时在不同页重复写入该图片问题,如何在通过sheet获取绘图对象清除该图片
java·pdf·excel
现在,此刻7 小时前
leetcode 11. 盛最多水的容器 -java
java·算法·leetcode
DKPT7 小时前
Java设计模式之开闭原则介绍与说明
java·设计模式·开闭原则
hyy27952276848 小时前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
布朗克1688 小时前
Spring Boot项目通过Feign调用三方接口的详细教程
java·spring boot·feign
Arva .8 小时前
Spring基于XML的自动装配
xml·java·spring
帅得不敢出门10 小时前
Android Framework定制长按电源键关机的窗口
android·java·framework
fatfishccc10 小时前
循序渐进学 Spring (上):从 IoC/DI 核心原理到 XML 配置实战
xml·java·数据库·spring·intellij-idea·ioc·di