单例模式
-
原则:保证一个类只会有一个实例,全局唯一。
-
特征:
- 构造函数私有化(确保不能在该类的外部通过构造函数来创建对象)
- 提供获取单例的公有静态方法;
- 使用static修饰单例的引用变量;
-
实现模式:
- 饿汉模式:在类加载到内存中时完成实例化,缺点就是可能会浪费内存空间,如果该类从始至终都没有被使用的话;
- 懒汉模式:单例被延迟实例化,只有在尝试获取该实例时才会完成实例化;缺点就是可能会带来线程安全问题;
-
饿汉式代码实现;
- 由于创建时完成了实例化所以不会有线程安全问题;
public class HungryMode { private HungryMode(){}; // 构造函数私有化 private static HungryMode singleton = new HungryMode(); // 在加载类的过程中完成单例的创建 public static HungryMode getSingleton(){ // 提供获取单例的公有静态方法 return singleton; } }
- 由于创建时完成了实例化所以不会有线程安全问题;
-
懒汉式代码实现(线程不安全式)
- 在多线程场景下,这中写法是线程不安全的,一个线程执行了
if(singleton == null)
语句,结果为true,但还没有来得及继续往下执行以创建实例,切换到了另一个线程又执行了这个判断,也会为true继续执行以创建实例,这样就会创建两个实例,违反了单一实例原则,代码如下: -
public class LazyMode { private LazyMode(){}; // 构造函数私有化 private static LazyMode singleton; // 延迟加载 public static LazyMode getInstance(){ // 公有静态方法提供单例 if(singleton == null){ singleton = new LazyMode(); } return singleton; } }
- 在多线程场景下,这中写法是线程不安全的,一个线程执行了
-
懒汉式代码实现(双重校验锁-线程安全)
-
加锁操作只对实例化那一部分代码进行,而不是对整个getSingleton()方法进行加锁,从而避免了单例已经存在之后的加锁操作;也就是即使单例已经存在,但是当一个线程进入该方法后,其他试图进入该方法的线程都必须等待,这会让线程阻塞更长时间;
-
volatile防止指令重排,主要由于
singleton = new LazyMode();
不是原子操作,此行代码分为有以下几步执行:- 给实例分配内存;
- 初始化实例;
- singleton 指向分配的内存地址;
注意 由于指令重排可能导致执行顺序变为1->3->2,在多线程环境下,例如线程1先执行了1和3,然后切换到线程2,线程2调用getSingleton()后发现,singleton不为空,然后返回了一个未被初始化的单例;
-
双重校验指的是两个
if(singleton == null)
,第一次校验是用于判断单例是否已存在; 第二次校验则是,如果当前单例未创建,然后两个线程都执行了第一个if语句并进入了语句块内,虽然语句块内会有加锁操作,但是两个线程也都是会执行实例的创建,只是先后而已,这就会创建两个实例; -
代码实现:
public class LazyMode { private static volatile LazyMode singleton; private LazyMode(){}; public static LazyMode getSingleton(){ if(singleton == null){ synchronized (LazyMode.class){ // synchronized 修饰代码块 if(singleton == null){ singleton = new LazyMode(); } } } return singleton; } }
-