设计模式--单例模式(Java版本)



前言

设计模式学习记录,如有不足烦请包涵


一、什么是单例模式?

创建型模式的一种,用来创建独一无二,只能有一个实例的对象。这个类没有公开的构造器,提供了访问其唯一的对象静态方法,不需要实例化该类的对象。

二、应用及实现方式

2.1 应用及使用场景

2.1.1 应用实例

1、一个班级只有一个班主任。
2、Windows 文件处理
3、打印机设备驱动

2.1.2 使用场景

1、要求生产唯一序列号。
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
4、注册表设置的对象,保证全局资源只有一份。
5、线程池、缓存、对话框
6、Spring中创建的Bean实例默认都是单例。

2.2 实现方式

单例模式的实现,通常需要3个部分,私有的构造器、一个私有静态变量和一个公开的静态方法,提供一个全局的访问点。

2.2.1 懒汉式–需要用到实例时再创建

/**
 * @author nzh
 * @desc
 * @date 2022/9/5 9:38
 */
public class SingleTon {
    //私有静态变量
    private static SingleTon instance;

    //私有构造器
    private SingleTon() {
    }

    //获取实例的静态方法
    public static SingleTon getInstance() {
        if (instance == null) {
            instance = new SingleTon();
            // 用于测试的代码
            // System.out.println(Thread.currentThread().getName() + "创建了一个实例");
        }
        return instance;
    }
}

这种方式会带来的问题是,多线程情况下不安全,可能线程A已经进入方法,但是还未实例化,这时线程B也通过了判断,进入方法准备实例化对象。因此创建了多个对象。

测试代码:

@Slf4j
@SpringBootTest(classes = TestSpringApplication.class)
@RunWith(SpringRunner.class)
public class SpringBootTest1 {
    
    @Test
    public void compareInstance() {
        for (int i = 0; i < 10; i++) {
            run();
        }
        System.out.println("这是" + Thread.currentThread().getName());
    }

	//匿名内部类写法
    public void run() {
        new Thread(() -> {
            SingleTon.getInstance();
            System.out.println(Thread.currentThread().getName() + "执行了");
        }).start();
    }
}

运行结果:
在这里插入图片描述

2.2.2 懒汉式–解决多线程问题,获取实例方法加锁,变成同步方法

改动代码如下,其余部分同上:

 	//获取实例的静态方法
    // synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把
    public static synchronized SingleTon getInstance() {
        if (instance == null) {
            instance = new SingleTon();
            // 用于测试的代码
            // System.out.println(Thread.currentThread().getName() + "创建了一个实例");
        }
        return instance;
    }

测试结果:
在这里插入图片描述

这样就解决了多线程问题,但同样因为是静态方法上的锁,所有线程都要等待其释放锁,才能执行下一步,而实际上只有第一次执行方法时,才需要同步,这样写就极大降低了性能。假如对getInstance()性能要求不高,可以考虑;改善性能有两种一种是饿汉式,一种是双重校验锁

Sychronized关键字注意点:

  1. 一把锁只能同时被一个线程获取,没有获得锁的线程只能等待;
  2. 每个实例都对应有自己的一把锁(this),不同实例之间互不影响;
  3. 锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象公用同一把锁 synchronized修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁

2.2.3 饿汉式–解决多线程性能

public class HungerSingleTon {
    //直接创建,避免了多线程问题
    private static HungerSingleTon instance = new HungerSingleTon();

    private HungerSingleTon() {}
    //直接返回创建好的实例对象
    public static HungerSingleTon getInstance() {
        return instance;
    }
}

这种方法十分简单,但类加载时,就完成初始化,容易产生垃圾对象,造成内存浪费。如果是一定会用到的对象,或者创建运行该实例时负担不大,可以使用。

2.2.4 懒汉式–解决多线程性能,双重校验锁

public class SingleTon {
    //私有静态变量
    //volatile保证多线程下 该变量的可见性 不会发生指令重排序
    private volatile static SingleTon instance;
    //私有构造器
    
    private SingleTon() {
    }
    //获取实例的静态方法
    public static SingleTon getInstance() {
        //只有第一次同步,才会走完这个方法
        if (instance == null) {
            // 所有线程需要的锁都是同一把
            synchronized (SingleTon.class) {
                if (instance == null) {
                    instance = new SingleTon();
                    // 用于测试的代码
                    System.out.println(Thread.currentThread().getName() + "创建了一个实例");
                }
            }
        }
        return instance;
    }
}

利用双重校验锁,会先检查是否创建了实例,尚未创建才会往下执行同步方法,极大的提高了代码性能,减少getInstance()的时间耗费。

ps:序列化/反序列化能破解单例模式,这种时候可以考虑枚举方式实现单例。

2.2.5 静态内部类

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}

能实现和双重校验锁一样的效果,但相对而言只适用于静态域,双重校验锁可以在实例域中实现

2.2.6 枚举类

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

代码简洁,不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化


总结

参考了许多书籍和博客里的知识点和代码,仅作为学习记录,还有很多不足的地方,深入学习之后会再回来修改细节,不足之处望能批评指正。
参考内容:(包含但不限于)
《深入浅出设计模式》
《大话设计模式》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值