1、首先解释下什么是单例模式,单例模式即当前进程确保一个类全局只有一个实例。
2、什么时候用单例模式?即单例模式的使用场景有哪些?
- 要求生成唯一序列号的环境
- 在整个项目中需要一个共享访问点或共享数据
- 创建一个对象需要消耗的资源过多
- 需要定义大量的静态常量和静态方法(如工具类)的环境
-
场景:在计算机系统中,
线程池
、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。
3、单例模式的优缺点?
优点:
- 单例模式在内存中只有一个实例,减少了内存开支
- 单例模式只生成一个实例,所以减少了系统的性能开销
- 单例模式可以避免对资源的多重占用
- 单例模式可以在系统设置全局的访问点
缺点
- 单例模式一般没有接口,扩展很困难
- 单例模式不利于测试
- 单例模式与单一职责原则有冲突。
4. 单例模式常见的写法:
一、饿汉式(线程比较安全)
/**
* @author 李告
* @Description: 饿汉式(线程安全)
* @data 2022 年 08 月 25 日 11:22
*/
public class Singleton_1 {
//属性私有化,以此来创建一个对象(因为类在被加载的时候,static修饰的内容也跟着被加载,但是只会被加载一次)
private static Singleton_1 instance = new Singleton_1();
// 构造器私有化
private Singleton_1() {
}
//设置外界可以访问的方法,以此返回给外界一个实例化对象,设置为static是让外界可以通过类名.方法名的方式创建实例
public static Singleton_1 getInstance() {
return instance;
}
}
但是因为
instance
是个静态变量,所以它会在类加载的时候完成实例化,不存在线程安全的问题。
这种方式不是懒加载,不管我们的程序会不会用到,它都会在程序启动之初进行初始化。所以接下来用懒汉式实现懒加载效果。懒汉式即当我们需要用到的时候才会加载。
二、懒汉式(线程不安全)
/**
* @author 李告
* @Description: 懒汉式基础版(线程不安全)
* @data 2022 年 08 月 25 日 17:56
*/
public class Singleton_2 {
// 设置对象的初始化为null
private static Singleton_2 instance=null;
//构造器私有化
private Singleton_2() {
}
//设置外界可以访问的方法,以此返回给外界一个实例化对象,设置为static是让外界可以通过类名.方法名的方式创建实
public static Singleton_2 getInstance() {
// 判断实例是否为空,第一次调用此方法时为空则创建实例,第二次判断时,此时实例已存在,返回之前的实例
if (instance == null) {
instance = new Singleton_2();
}
return instance;
}
}
但是如此般的懒汉式又会导致线程安全问题,可能存在这样的问题:
一个线程判断
instance==null
,开始初始化对象;还没来得及初始化对象时候,另一个线程访问,判断
instance==null
,也创建对象。最后的结果,就是实例化了两个Singleton对象。这不符合我们单例的要求啊?那如何解决呢?
三、懒加载(加锁)
/**
* @author 李告
* @Description: 懒汉式(加锁)
* @data 2022 年 08 月 25 日 19:01
*/
public class Singleton_3 {
private static Singleton_3 instance;
private Singleton_3() {
}
//添加同步锁,一个线程进来后,其他线程需要此类线程结束后方可进入此方法
public synchronized static Singleton_3 getInstance() {
if (instance == null) {
instance = new Singleton_3();
}
return instance;
}
}
但是这种把锁直接方法上的办法,所有的访问都需要获取锁,导致了资源的浪费。所以在此基础上可以加以改进。
四、懒汉式(Double Check 双重检查锁)
我们把
synchronized
加在了方法的内部,一般的访问是不加锁的,只有在
instance==null
的时候才加锁。
/**
* @author 李告
* @Description: Double Check 单例模式 双重检查锁
* @data 2022 年 08 月 25 日 19:15
*/
public class Singleton_4 {
//volatile修饰,防止指令重排
private static volatile Singleton_4 instance;
private Singleton_4() {
}
public static Singleton_4 getInstance() {
//第一重校验,检查实例是否存在
if (instance == null) {
//同步块
synchronized (Singleton_4.class) {
//第二重校验,检查实例是否存在,如果不存在才真正创建实例
if (instance == null) {
instance = new Singleton_4();
}
}
}
return instance;
}
}
观察这种实现方式,大家有没有想过我们为什么要双重检查?如果不双重校验。如果两个线程一起调用getInstance方法,并且都通过了第一次的判断instance==null,那么第一个线程获取了锁,然后实例化了instance,然后释放了锁,然后第二个线程得到了线程,然后马上也实例化了instance。这就不是单例模式了。
五、静态内部类方式
不仅能实现懒加载、线程安全,而且JVM还保持了指令优化的能力。Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会加载静态内部类InnerSingleton类,从而完成Singleton的实例化。类的静态属性只会在第一次加载类的时候初始化,同时类加载的过程又是线程互斥的,JVM帮助我们保证了线程安全。
/**
* @author 李告
* @Description: 单例模式 (静态内部类方式)
* @data 2022 年 08 月 25 日 19:31
*/
public class Singleton_5 {
// 构造器私有化
private Singleton_5() {
}
//静态内部类
private static class InnerSingleton {
private static final Singleton_5 instance = new Singleton_5();
}
public static Singleton_5 getInstance() {
return InnerSingleton.instance;
}
}
六、CAS方式
/**
* @author 李告
* @Description: 单例模式(CAS方式)
* @data 2022 年 08 月 25 日 19:34
*/
public class Singleton_6 {
/*
sychronized是一种悲观锁,而CAS是乐观锁,相比较,更轻量级,但是这种写法也比较罕见,CAS存在忙等的问题,可能会造成CPU资源的浪费。
*/
private static final AtomicReference<Singleton_6> INSTANCE = new AtomicReference<Singleton_6>();
private Singleton_6() {
}
public static final Singleton_6 getInstance() {
// 等待
while (true) {
Singleton_6 instance = INSTANCE.get();
if (null == instance) {
INSTANCE.compareAndSet(null, new Singleton_6());
}
return INSTANCE.get();
}
}
}
七、枚举方式
/**
* @author 李告
* @Description: 单例模式( 枚举 )
* @data 2022 年 08 月 25 日 19:37
*/
public enum Singleton_7 {
//定义一个枚举,代表了Singleton的一个实例
INSTANCE;
public void anyMethod() {
System.out.println("枚举方式实现单例模式");
}
/*
外部类调用方式
@Test void anyMethod() { Singleton_7.INSTANCE.anyMethod(); }
*/
}
由于枚举类的特点等,使这种写法解决了最主要的问题:线程安全、由串化、单实例。