title: 懒汉、饿汉单例模式-完整的懒汉单例模式实现
date: 2019-06-07 23:22:50
tags: [设计模式,多线程]
本文主要介绍懒汉和饿汉单例模式实现以及多线线程场景下完整的懒汉单例模式的实现
一般实现
- 饿汉式单例(一开始就实例化)
class Singleton {
private static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return singleton;
}
}
- 懒汉式单例(用到的时候在实例化)
/**
* 懒汉式单例
* 存在线程安全问题
*/
class LazySingleton {
private static LazySingleton singleton;
private LazySingleton() {}
public static LazySingleton getInstance() {
//线程1、2同时进来都是null,所以new出来不止一个对象
if (singleton == null) {
singleton = new LazySingleton();
}
return singleton;
}
}
线程安全的懒汉式单例模式
- 正如上面的经典懒汉单例模式,在多线程场景下,线程1和线程2会同时进入if语句,这样就会产生两个对象,这样违背了单例的思想
那么应该如何解决呢?
先观察下面的代码,然后解释
/**
* 最完整的懒汉式单例模式(双重加锁、双重检查 单例模式)
* 线程安全的懒汉单例模式,写单例直接写这个
*/
class ThreadSafeLazySingleton {
private String str;
/**
* 加上volatile是最完整的懒汉式单例模式
* 第二层锁,禁止指令重排
* 第二层保证不管多少线程拿到singleton对象,一定是完全初始化好的对象
*/
private static volatile ThreadSafeLazySingleton singleton;
private ThreadSafeLazySingleton() {
str = "hello";
}
public ThreadSafeLazySingleton(String str) {
this.str = str;
}
public static ThreadSafeLazySingleton getInstance() {
/* 线程1、2同时进来都是null,所以new出来不止一个对象
* 双重检查的第一次检查
* single checked :第一次检查
* 这个检查的意义在于可以加快返回对象的时间,如果是第二次
* 获取对象,可以直接返回这个单例,而不需要在执行下面的同
* 步代码块
*/
if (singleton == null) {
/*
* 不在single checked外加synchronized 是因为力度要
* 轻,我们还需在进入第一次检查在初始化单例对象前做一些
* 准备工作
* 如果将getInstance加上synchronized变成同步方法,
* 这样效率会很低,因为方法是同步的,多个线程访问需
* 要等待
* 如果不加这层锁,就有可能有多个线程进入double checked
* 中,从而产生多个对象
*/
synchronized (ThreadSafeLazySingleton.class) {
/* double checked :第二次检查
* 这一次检查是必须的,没有这次检查一定会产多个对象
*/
if (singleton == null) {
singleton = new ThreadSafeLazySingleton();
}
}
}
return singleton;
}
}
-
程序的注释中解释了双重检查,和双重加锁的位置,难以理解
volatile
禁止指令重排,解释如下- java创建对象的时候进行如下三步操作
- 在堆上分配空间
- 属性初始化
- 引用指向对象(这个时候
singleton
已经不为null
)
-
当第一个线程执行
singleton = new Singleton();
的时候如果进行指令重排,3可以再2前面执行,singleton
已经不为null,这个时候线程2调用getInstance
直接return singleton
这个时候没有初始化,出现没有赋值的情况,所以线程2得到的不是完整的对象
为了解决这一问题,引入volatile
禁止了指令重排,保证了2一定在3前面执行,这样线程2得到的一定是初始化完成的对象