创造者模式:工厂方法、抽象工厂、生成器、原型、单例

这类模式提供创建对象的机制,能够提升已有代码的灵活性和可复用性

单例模式

这是五个创建型模式中的最后一个。

在设计模式中按照不同的处理方式共包含三大类:创建型模式、结构性模式和行为模式

一、单例模式介绍

单例模式主要解决的是:一个全局使用的类频繁的创建和消费。

二、案例场景

日常开发可见:

  1. 数据库的连接池不会反复创建。
  2. spring中的一个单例模式bean的生成和使用。
  3. 在我们平常的代码中需要设置全聚德一些属性保存。

三、7种单例模式的实现

单例模式的实现方式较多,主要在实现上是否支持懒汉模式、是否线程安全中运用各项技巧。

0.静态类使用

package org.levtio.demo.design;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* @Author: LuoMingDong
* @Date: 2023/9/21 13:54
* @Describe: 静态类使用
*/
public class Singleton_00 {
public static Map<String,String> cache = new ConcurrentHashMap<>();
}

  1. 以上这种方式很常见,这样的静态类的方式可以再第一次运行的时候直接初始化Map类,不需要延迟加载再使用。
  2. 在不需要维持任何状态下,仅仅用于全局访问,这个使用静态类的方式更加方便。
  3. 但是如果需要被继承以及需要维持一定特定状态的情况下,就是和使用单例模式。

1.懒汉模式(线程不安全)

首先我先来介绍一下懒汉模式(Lazy Initialization):这是一种延迟实例化对象的设计模式。它的主要目的是在需要时才创建对象,而不是在程序启动时就创建对象。

package org.levtio.demo.design;

/**
* @Author: LuoMingDong
* @Date: 2023/9/21 13:57
* @Describe: 懒汉模式(线程不安全)
*/
public class Singleton_01 {
private static Singleton_01 instance;

private Singleton_01() {

}

public static Singleton_01 getInstance() {
if (instance != null) {
return instance;
}
instance = new Singleton_01();
return instance;
}
}

在这个实例中,Singleton_01类是一个单例类,只能创建一个实例。
instance变量是一个静态变量,用于保存实例。
getInstance()方法是一个静态方法,用于获取LazySingleton类的实例。
在第一次调用getInstance()方法时,如果instance为null,则创建一个新的实例。
之后的调用将直接返回已经创建的实例。

  1. 单例模式有一个特点就是不允许外部直接创建,也就是new一个,因此这里在默认的构造函数上添加了私有熟悉private。
  2. 线程不安全,当多个访问者同时去获取对象实例时会造成多个同样的实例并存。

2.懒汉模式(线程安全)

package org.levtio.demo.design;

/**
* @Author: LuoMingDong
* @Date: 2023/9/21 14:44
* @Describe:
*/
public class Singleton_02 {
private static Singleton_02 instance;
private Singleton_02(){

}
public synchronized static Singleton_02 getInstance(){
if (instance != null) {
return instance;
}
instance = new Singleton_02();
return instance;
}
}

此种模式就是加上了synchronized关键字,把锁加到方法上后,所有的访问都需要锁占用,导致资源的浪费。

3.饿汉模式(线程安全)

package org.levtio.demo.design;

/**
* @Author: LuoMingDong
* @Date: 2023/9/21 15:21
* @Describe: 饿汉模式,程序初始化时候加载
*/
public class Singleton_03 {
private static Singleton_03 instance = new Singleton_03();
private Singleton_03(){

}
public static Singleton_03 getInstance(){
return instance;
}
}
  1. 此种方式与我们开头的第一个实例化Map基本一致,在程序启动的时候直接运行加载,后续有外部需要使用的时候获取即可。
  2. 但是这种方式不是懒加载,是否用到都会耗费资源。

4.使用类的内部类(线程安全)

package org.levtio.demo.design;

/**
* @Author: LuoMingDong
* @Date: 2023/9/21 15:21
* @Describe: 首推
*/
public class Singleton_04 {
private static class SingletonHolder{
private static Singleton_04 instance = new Singleton_04();
}
private Singleton_04(){

}
private static Singleton_04 getInstance(){
return SingletonHolder.instance;
}
}
  1. 使用类的静态内部类实现的单例模式,既保证了线程安全又保证了懒加载,同时不会因为加锁的方式耗费性能。
  2. 这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是一个类的构造方法在多线程环境下可以被正确的加载。
  3. 此方法推荐

5.双重锁校验(线程安全)

package org.levtio.demo.design;

/**
* @Author: LuoMingDong
* @Date: 2023/9/21 15:21
* @Describe:
*/
public class Singleton_05 {
private static Singleton_05 instance;
private Singleton_05(){}
public static Singleton_05 getInstance(){
if (instance != null){
return instance;
}
synchronized (Singleton_05.class){
if (instance == null){
instance = new Singleton_05();
}
}
return instance;
}
}
  1. 双重锁的方式是方法级锁的优化,减少了部分获取实例的耗时。
  2. 同时这种方式也满足了懒加载。

6.CAS[AtomicReference](线程安全)

package org.levtio.demo.design;

import java.util.concurrent.atomic.AtomicReference;

/**
* @Author: LuoMingDong
* @Date: 2023/9/21 15:22
* @Describe:
*/
public class Singleton_06 {
private static final AtomicReference<Singleton_06> INSTANCE = new
AtomicReference<Singleton_06>();
private static Singleton_06 instance;
private Singleton_06() {
}
public static final Singleton_06 getInstance() {
for (; ; ) {
Singleton_06 instance = INSTANCE.get();
if (null != instance) {
return instance;
}
INSTANCE.compareAndSet(null, new Singleton_06());
return INSTANCE.get();
}
}

public static void main(String[] args) {
System.out.println(Singleton_06.getInstance());
System.out.println(Singleton_06.getInstance());

// 运行结果相同 org.levtio.demo.design.Singleton_06@2503dbd3
// org.levtio.demo.design.Singleton_06@2503dbd3

}
}
  1. java并发库提供了很多原子类来持之并发访问的数据安全性:AtomicIntegerAtomicBooleanAtomicLongAtomicReference
  2. AtomicReference可以封装引用一个V实例,支持并发访问
  3. 使⽤CAS的好处就是不需要使⽤传统的加锁⽅式保证线程安全,⽽是依赖于CAS的忙等算法,依赖
    于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外
    的开销,并且可以⽀持较⼤的并发性
  4. 当然CAS也有⼀个缺点就是忙等,如果⼀直没有获取到将会处于死循环中。

7.枚举单例(线程安全)

package org.levtio.demo.design;

/**
* @Author: LuoMingDong
* @Date: 2023/9/21 15:22
* @Describe:
*/
public enum Singleton_07 {
INSTANCE;
public void test(){
System.out.println("oi");
}

}

这种方式解决了最主要的:线程安全、自由串行化、单一实例。

调用方法:

package org.levtio.demo.design.test;

import org.junit.Test;
import org.levtio.demo.design.Singleton_07;

/**
* @Author: LuoMingDong
* @Date: 2023/9/21 15:40
* @Describe:
*/
public class ApiTest {
@Test
public void test(){
Singleton_07.INSTANCE.test();
}
}

这种写法在功能上与共有域⽅法相近,但是它更简洁,⽆偿地提供了串⾏化机制,绝对防⽌对此实例 化,即使是在⾯对复杂的串⾏化或者反射攻击的时候。虽然这种⽅法还没有⼴泛采⽤,但是单元素的枚举类型已经成为实现Singleton的最佳⽅法。

但也要知道此种⽅式在存在继承场景下是不可⽤的

四、总结

  1. 虽然只是⼀个很平常的单例模式,但在各种的实现上真的可以看到java的基本功的体现,这⾥包括了;懒汉、饿汉、线程是否安全、静态类、内部类、加锁、串⾏化等等。
  2. 在平时的开发中如果可以确保此类是全局可⽤不需要做懒加载,那么直接创建并给外部调⽤即可。但如果是很多的类,有些需要在⽤户触发⼀定的条件后(游戏关卡)才显示,那么⼀定要⽤懒加载。线程的安全上可以按需选择。