JAVA【设计模式】单例模式

一、定义

在这里插入图片描述

单例模式是java中最简单的设计模式之一,属于创建型模式,提供了创建一个全局实例对象的最佳方式

二、示例:

模拟场景:

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

三、7种单例模式的实现

0. 静态类使⽤

在不需要维持任何状态下,仅仅⽤于全局访问,这个使⽤使⽤静态类的⽅式更加⽅便

package com.qf.design.create.singleton;

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

public class SingleTon00 {
    /**
     * 静态类的使用
     * 这样静态类的⽅式可以在第⼀次运⾏的时候直
     * 接初始化Map类,同时这⾥我们也不需要到延迟加载在使⽤。
     * 在不需要维持任何状态下,仅仅⽤于全局访问,这个使⽤使⽤静态类的⽅式更加⽅便。
     * 但如果需要被继承以及需要维持⼀些特定状态的情况下,就适合使⽤单例模式
     */
    public static Map<String,String> map=new ConcurrentHashMap<>();
}

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

高并发的场景下就会造成多个同样的实例并存,从⽽没有达到单例的要求

package com.qf.design.create.singleton;

public class SingleTon01 {
    /**
     *懒汉式(线程不安全)
     * 单例模式有⼀个特点就是不允许外部直接创建,也就是 new Singleton_01() ,因此这⾥在默认
     * 的构造函数上添加了私有属性 private 。
     * ⽬前此种⽅式的单例确实满⾜了懒加载,但是如果有多个访问者同时去获取对象实例你可以想象成
     * ⼀堆⼈在抢厕所,就会造成多个同样的实例并存,从⽽没有达到单例的要求。
     */
    private static SingleTon01 instance;

    private SingleTon01(){}

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

2.饿汉式(线程安全)

饿汉式是线程安全的,但是它在一开始就被加载,如果是大的对象情况下,可能开始就占用了很大的内存空间

package com.qf.design.create.singleton;

public class SingleTon02 {

    /**
     * 饿汉式(线程安全)
     * 此种⽅式与我们开头的第⼀个实例化 Map 基本⼀致,在程序启动的时候直接运⾏加载,后续有外
     * 部需要使⽤的时候获取即可。
     * 但此种⽅式并不是懒加载,也就是说⽆论你程序中是否⽤到这样的类都会在程序启动之初进⾏创
     * 建。
     * 那么这种⽅式导致的问题就像你下载个游戏软件,可能你游戏地图还没有打开呢,但是程序已经将
     * 这些地图全部实例化。到你⼿机上最明显体验就⼀开游戏内存满了,⼿机卡了,需要换了。
     */
    private static SingleTon02 instance=new SingleTon02();

    private SingleTon02(){}

    public static SingleTon02 getInstance(){
        return instance;
    }
}

3.懒汉式(线程安全)

此种模式虽然是安全的,但由于把锁加到⽅法上后,所有的访问都因需要锁占⽤导致资源的浪费

package com.qf.design.create.singleton;

public class SingleTon03 {

    /**
     * 懒汉式(线程安全)
     * 所在方法上
     * 此种模式虽然是安全的,但由于把锁加到⽅法上后,所有的访问都因需要锁占⽤导致资源的浪费。
     * 如果不是特殊情况下,不建议此种⽅式实现单例模式。
     */
    private static SingleTon03 instance;

    private SingleTon03(){}

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

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

使用类的静态内部类实现的单例模式,既保证了线程安全有保证了懒加载,同时不会因为加锁的⽅式耗费性能

package com.qf.design.create.singleton;

public class SingleTon04 {
    /**
     * 使用类的内部类(线程安全)
     * 使用类的静态内部类实现的单例模式,既保证了线程安全有保证了懒加载,同时不会因为加锁的⽅
     * 式耗费性能。
     * 这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是个类的构造方法在多线程环
     *此种⽅式也是⾮常推荐使⽤的⼀种单例模式
     *
     */
    private static class SingleHolder{
        private static SingleTon04 instance=new SingleTon04();
    }

    private SingleTon04(){}

    public static SingleTon04 getInstance(){
        return SingleHolder.instance;
    }
}

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

双重校验减少了实例的创建消耗,减小了锁的力度

package com.qf.design.create.singleton;

public class SingleTon05 {
    /**
     * 双重校验锁(线程安全)
     * 双重校验减少了实例的创建消耗
     * 满足了懒加载
     */
    private static volatile SingleTon05 instance;

    private SingleTon05(){}

    public static SingleTon05 getInstance(){
        if (instance!=null) return instance;
        else synchronized (SingleTon05.class){
            if (instance==null){
                instance=new SingleTon05();
            }
            return instance;
        }
    }
}

6.CAS(线程安全)

java并发库提供了很多原⼦类来⽀持并发访问的数据安全性; AtomicIntegerAtomicBooleanAtomicLongAtomicReferenceAtomicReference 可以封装引⽤⼀个V实例,⽀持并发访问如上的单例⽅式就是使⽤了这样的⼀个特点。

使⽤CAS的好处就是不需要使⽤传统的加锁⽅式保证线程安全,⽽是依赖于CAS的忙等算法,依赖于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以⽀持较⼤的并发性。

package com.qf.design.create.singleton;

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;

public class SingleTon06 {

    /**
     * CAS(线程安全)
     * java并发库提供了很多原⼦类来⽀持并发访问的数据安全
     * 性; AtomicInteger 、 AtomicBoolean 、 AtomicLong 、 AtomicReference 。
     * AtomicReference 可以封装引⽤⼀个V实例,⽀持并发访问如上的单例⽅式就是使⽤了这样的⼀个
     * 特点。
     * 使⽤CAS的好处就是不需要使⽤传统的加锁⽅式保证线程安全,⽽是依赖于CAS的忙等算法,依赖
     * 于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外
     * 的开销,并且可以⽀持较⼤的并发性。
     * 当然CAS也有⼀个缺点就是忙等,如果⼀直没有获取到将会处于死循环中。
     *
     */

    private static final AtomicReference<SingleTon06> INSTANCE = new
            AtomicReference<SingleTon06>();

    private static SingleTon06 instance;

    private SingleTon06() { }

    public static final SingleTon06 getInstance() {
        for (; ; ) {
            SingleTon06 instance = INSTANCE.get();
            if (null != instance) return instance;
            INSTANCE.compareAndSet(null, new SingleTon06());
            return INSTANCE.get();
        }
    }

}

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

使⽤枚举的⽅式解决单例模式,此种⽅式可能是平时最少⽤到的,解决线程安全、⾃由串⾏化、单⼀实例

package com.qf.design.create.singleton;

public enum SingleTon07 {
    /**
     * 枚举单例(线程安全)
     * 使⽤枚举的⽅式解决单例模式,此种⽅式可能是平时最少⽤到的。
     * 这种⽅式解决了最主要的;线程安全、⾃由串⾏化、单⼀实例。
     */
    INSTANCE;
    public void test(){
        System.out.println("hello~~");
    }
}

总结:
虽然只是⼀个很平常的单例模式,但在各种的实现上真的可以看到java的基本功的体现,这⾥包括了懒汉、饿汉、线程是否安全、静态类、内部类、加锁、串⾏化等等

在平时的开发中如果可以确保此类是全局可⽤不需要做懒加载,那么直接创建并给外部调⽤即可。
但如果是很多的类,有些需要在⽤户触发⼀定的条件后(游戏关卡)才显示,那么⼀定要⽤懒加载
线程的安全上可以按需选择

posted @ 2022-08-30 22:40  雾托邦  阅读(124)  评论(0编辑  收藏  举报