《Java-SE-第二十三章》之单例模式

简介: 《Java-SE-第二十三章》之单例模式

文章目录

单例模式概述

饿汉模式

懒汉模式单线程版

懒汉单例多线程版

枚举实现单例

单例模式概述

单例模式是设计模式中的一种,其作用能保证某个类在程序中只存在唯一一份实例,而不会创建多份实例。单例模式具体的实现方式, 分成 “饿汉” 和 “懒汉” 两种.。饿汉模式中的饿不并不是真饿了,而是说提前把单例类更创建好。懒汉模式中的懒则是当需要使用到单例类的时候才创建单例对象。这就类似于,每次吃饭的时候,已经提前把碗洗了有碗用,这就是"饿汉"。"懒汉"则是要用碗赶紧去把碗洗了。单例模式中的单例类有且只有一份,static修饰的成员变量是属于类的,也是只有一份,所以我们使用static修饰的成员变量保存 到实例对象的引用变量。

饿汉模式

在单例模式中一个类只有一个实例对象,所以避免外部通过构造方法创建对象,所以需要才构造方法私有,防止创建出多个对象。又因为我们是使用static来保存实例对象的引用,所以需要提供一个静态方法获取唯一的对象。

示例代码

public class HungrySingleton {
    /**
     * 类加载的时候创建出HungrySingleton对象,并用静态差成员变量保存。
     */
    private static HungrySingleton singleton = new HungrySingleton();
    private HungrySingleton() {
    }
    /**
     * 提供静态方法获取单例
     * @return
     */
    public static HungrySingleton getSingleton() {
        return singleton;
    }
}

上述的代码多线程环境下依旧是安全的,因为 getSingleton()方法中的return语句只涉及到读而不涉及修改。

懒汉模式单线程版

懒汉模式和饿汉模式相比就是创建实例的时机不一样,懒汉不是在类加载的时候创建而是在需要使用的时候创建。

示例代码

public class SlackerSingleton {
    private static SlackerSingleton singleton = null;
    private SlackerSingleton() {
    }
    /**
     * 提供静态方法获取单例
     *
     * @return
     */
    public static SlackerSingleton getSingleton() {
        //需要使用的时候创建对象
        if (singleton == null) {
            singleton = new SlackerSingleton();//真正创建的时机,创建好了,后续是直接返回。
        }
        return singleton;
    }
}

多线程的环境下,当t1线程进行比较singleton是否为空时,比较完之后,t1线程还没有创建实例。此时t2线程立马进来再次判断此时singleton依旧为空,导致t2线程也进行new操作,最终导致创建了多份实例。造成线程不安全的代码就是if语句以及实例的创建操作,所以我们需要对这段代码加锁。

懒汉单例多线程版

加锁后的代码

public class SlackerSingleton {
    private static SlackerSingleton singleton = null;
    private SlackerSingleton() {
    }
    /**
     * 提供静态方法获取单例
     *
     * @return
     */
    public static SlackerSingleton getSingleton() {
        //需要使用的时候创建对象
        synchronized (SlackerSingleton.class) {
            if (singleton == null) {
                singleton = new SlackerSingleton();
            }
        }
        return singleton;
    }
}

上述代码仅仅是针对首次创建实例的情况,如果singleton已经创建好了,if语句就啥用了,但是第二次进入该方法会去获取锁,而获取锁海外释放锁的是耗时耗力的。所以可以在加锁之前进行判断该实例是否已经创建好了,没有则获取锁,创建好了直接返回。

双重if的代码

public class SlackerSingleton {
    private static SlackerSingleton singleton = null;
    private SlackerSingleton() {
    }
    /**
     * 提供静态方法获取单例
     *
     * @return
     */
    public static SlackerSingleton getSingleton() {
        //需要使用的时候创建对象
        if (singleton == null) {//判断是否需要加锁
            synchronized (SlackerSingleton.class) {
                if (singleton == null) {//判断是否需要创建实例
                    singleton = new SlackerSingleton();
                }
            }
        }
        return singleton;
    }
}

优化到这里依旧是存在问题的,和这个锅就是编译器的了。上述代码需要判断isingleton == null,这个操作在多线程的情况下是非常频繁的,导致CPU频繁的从内存读取(Load)到寄存器然后进行比较(CMP),此时就会进行优化,线程不会去读取内存中数据,而是会从寄存器中读取数据,一旦当singleton值变化时,线程是感知不到的,就会造成内存可见性问题,除此之外,还有另一个问题就是由new SlackerSingleton();引起来的,创建对象大概可以分为三步:①:JVM为对象分配一块内存S,②:在内存S上对对象进行初始化,③:将内存S的地址赋值singleton变量。为了引出bug,我们假设有两个线程t1和t2同时使用getSingleton()方法。

正常情况按照对象创建①②③来执行。

第一步:线程t1直接运行到singleton = new SlackerSingleton();并且一口气执行完了①②③。

第二步:线程t2进行该方法判断singleton就直接返回了。

第三步:线程t2可以正常使用。

出现bug的情况按对象创建的步骤①③②来执行。

第一步:线程t1执行singleton = new SlackerSingleton();执行完①JVM为对象分配一块内存.③将内存的地址赋值给singleton 变量

第二步:线程t2进入该方法进行判断发现singleton不为空,拿到singleton 返回了。此时线程t2拿到的是singleton 的半成品对象,因为该对象没有初始化。

第三步:线程t2拿到该对象去使用就可能会出现异常。

综上所述,还存在两个隐含问题一个是内存可见性问题,另一个是指令重排序问题。解决这个问题只需要将静态变量加上volatile 即可。

volatile 修饰静态变量

public class SlackerSingleton {
    private static volatile SlackerSingleton singleton = null;
    private SlackerSingleton() {
    }
    /**
     * 提供静态方法获取单例
     *
     * @return
     */
    public static SlackerSingleton getSingleton() {
        //需要使用的时候创建对象
        if (singleton == null) {
            synchronized (SlackerSingleton.class) {
                if (singleton == null) {
                    singleton = new SlackerSingleton();
                }
            }
        }
        return singleton;
    }
}

此时多线程长场景下的懒汉模式完成。

枚举实现单例

因为枚举是天然的单例,并且枚举类型无法通过反射来获取封装的私有变量,非常安全。

示例代码

public enum Singleton {
     INSTANCE;
     public void businessMethod() {
          System.out.println("我是一个单例!");
     }
}

简单使用

public class SingletonDemo {
    public static void main(String[] args) {
        Singleton.INSTANCE.businessMethod();
    }
}

运行结果

相关文章
|
4月前
|
Java 编译器
单例模式---JAVA
“饿汉”模式 “懒汉”模式
101 1
|
16天前
|
设计模式 安全 Java
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
|
1月前
|
SQL 设计模式 安全
Java单例模式几种写法以及代码案例拿来直接使用
Java单例模式几种写法以及代码案例拿来直接使用
34 0
|
4天前
|
设计模式 消息中间件 安全
【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列
【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列
9 0
|
15天前
|
设计模式 存储 安全
Java 设计模式:深入单例模式的理解与应用
【4月更文挑战第27天】单例模式是一种常用的设计模式,在 Java 开发中扮演着重要角色。此模式的主要目的是保证一个类只有一个实例,并提供一个全局访问点。
20 0
|
19天前
|
设计模式 安全 Java
[设计模式Java实现附plantuml源码~创建型] 确保对象的唯一性~单例模式
[设计模式Java实现附plantuml源码~创建型] 确保对象的唯一性~单例模式
|
29天前
|
设计模式 存储 Java
Java设计模式:解释一下单例模式(Singleton Pattern)。
`Singleton Pattern`是Java中的创建型设计模式,确保类只有一个实例并提供全局访问点。它通过私有化构造函数,用静态方法返回唯一的实例。类内静态变量存储此实例,对外仅通过静态方法访问。
16 1
|
5月前
|
设计模式 Java
Java设计模式【一】:单例模式
Java设计模式【一】:单例模式
29 0
|
1月前
|
设计模式 缓存 安全
23种设计模式,单例模式的概念优缺点以及JAVA代码举例
【4月更文挑战第9天】在软件工程中,设计模式是为常见问题提出的典型解决方案。总共有23种设计模式,这些模式被分为三大类:创建型、结构型和行为型。单例模式是其中的一种创建型模式,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点来获取这个实例
20 7
|
1月前
|
安全 Java
Java四种单例模式优劣势以及线程安全
Java四种单例模式优劣势以及线程安全
17 1