单例模式学习笔记(详细)

单例模式

一、什么是单例模式

单例模式是一种常见的设计模式,定义是这个类只允许有一个实例对象存在

二、使用场景

购买东西时的购物车,window系统的回收站等等

三、实现方式

1. 懒汉式

public class Lazy {
	private static Lazy instance;
	private Lazy(){}
	public static Lazy getInstance(){
		if(instance == null){
			instance = new Lazy();
		}
		return instance;
	}
}

懒汉式,实行延迟加载,不会初始化,在需要用到实例的时候才去创建,用到的时候先检查实例存不存在,存在就返回,不存在创建一个返回,可以再单线程下使用,多线程下是不安全的,一个线程通过了判空判断的同时,另一个线程也通过了判空判断,就会同时创建多个实例,想要线程安全可以加synchronized关键字,但是会影响效率,不推荐。

  • 优点:延迟加载,不会浪费内存
  • 缺点:线程不安全,加synchronized会影响效率

2. 饿汉式

public class Hungry {
	private static Hungry instance = new Hungry();
	private Hungry(){}
	public static Hungry getInstance(){
		return instance;
	}
}

饿汉式,在初始化的时候创建实例,不会存在线程安全问题

  • 优点:线程安全
  • 缺点:没有延迟加载效果,如果没有用到这个实例就会浪费内存

3. 双检锁

public class DoubleCheck{
	private volatile static DoubleCheck instance;
	private DoubleCheck(){}
	public static DoubleCheck getInstance(){
		if(instance == null){
			synchronized(DoubleCheck.class){
				if(instance == null){
					instance = new DoubleCheck();
				}
			}
		}
		return instance;
	}		
}

双检锁,外面一层if判断对象存在就不会执行加锁代码,提高了效率,synchronized加上里面一层 if 保证了线程安全

  • 优点: 线程安全,延迟加载,效率高,推荐

为什么加volatile关键字

原因是 instance = new DoubleCheck(); 这句代码不是原子性的。

创建一个对象分为三步:

  1. 分配 instance 对象内存空间 N
  2. 在内存 N 初始化对象
  3. 把 N 的地址赋值给对象 instance

这时在实例instance指向N的时候,instance是不为空的

但是,编译时编译器可能会将2,3顺序重新排序,造成顺序为1-3-2

  1. 分配 instance 对象内存空间 N
  2. 把 N 的地址赋值给对象 instance
  3. 在内存 N 初始化对象

线程A,在内存 N 初始化对象之前就将 N 的地址赋值给了instance
这时线程B调用了getInstance方法,发现instance不为null
这个时候instance没有初始化对象,线程B会将这个未初始化的对象返回,线程B在使用instance对象时就会出现问题

使用volatile修饰instance可以防止指令重排序,就可以避免这种现象发生。

4. 静态内部类

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

静态内部类,在第一次使用时才会初始化内部类Singleton,创建实例,保证了只有一个实例,并实现了延迟加载,加上静态域是线程安全的,减少了synchronized的开销。

  • 优点:线程安全,延迟加载,没有锁开销,效率高,推荐。

四、总结

这四种是比较常见的单例模式实现方式

  • 懒汉式实现了延迟加载,效率较高,但是线程不安全,适合单线程的时候使用
  • 饿汉式在初始化的时候创建对象,保证了线程安全,但是在没有使用到这个对象的时候就浪费了内存空间,可以在多线程使用
  • 双检索整合了懒汉式和饿汉式的优点,是线程安全,又实现了延迟加载,可以在多线程使用
  • 静态内部类方式也是拥有懒汉式和饿汉式的优点,线程安全,延迟加载,还减少了锁的开销,提高了效率,推荐使用这种方式

猜你喜欢

转载自blog.csdn.net/qq_43247353/article/details/107775136
今日推荐