目录
饿汉式(Eager Singleton)
package main.singleton;
/**
* Created by leboop on 2018/11/27.
*/
public class EagerSingleton {
// static:getInstance方法是静态方法,只能调用静态变量
// final:不允许类的内部对该实例进行修改,保证单例
// getInstance未调用前,类加载的时候instance已经被实例化,实例化的有点着急,所以有些地方称之为“饿汉式”
private final static EagerSingleton INSTANCE = new EagerSingleton();
//private:私有化构造方法,不允许类的外部使用new创建实例
private EagerSingleton() {
}
//static声明方法的原因:外部无法创建类的实例,只能通过类来调用方法
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
/**
* 主函数入口,测试单例
*/
class SingletonMain {
public static void main(String[] args) {
EagerSingleton singleton1 = EagerSingleton.getInstance();
EagerSingleton singleton2 = EagerSingleton.getInstance();
//输出true
System.out.println(singleton1 == singleton2);
}
}
单例构造步骤:
1、构造方法EagerSingleton()私有化
这一步是保证单例的基础,禁止外部通过new方式创建多个实例。
2、static修饰getInstance成员方法
构造方法已经私有化,外部不可以通过构造方法创建实例对象,只能通过EagerSingleton.getInstance的方式获取对象,所以getInstance成员方法需要声明为static。
3、使用static和final修饰INSTANCE
getInstance方法是static方法,只能访问static成员变量,所以INSTANCE需要使用static修饰。final也是保证单例的基础,禁止内部其他地方修改INSTANCE实例。
4、维度分析
(1)线程安全性
饿汉式单例模式是线程安全的。INSTANCE作为类变量在初始化的过程中会被收集进<clinit>()方法中,该方法能够保证同步,所以INSTANCE在多线程的情况下只能被实例化一次。
(2)性能
如果EagerSingleton中成员变量比较少,且占用的内存资源不多,该方式也未尝不可;但是,如果EagerSingleton中成员变量占用资源较多,那么该方式会有些不妥。instance被ClassLoader加载后可能很长时间才被使用,意味着instance实例开辟的堆内存空间会驻留更久的时间。
(3)懒加载
该方式无法进行懒加载。
懒汉式(Lazy Singleton)
在饿汉式单例模式中,未调用getInstance方法就已经实例化了INSTANCE实例,有些时候我们并不需要实例,而只是通过类来调用其他静态方法,事先创建INSTANCE实例就有些多余,所以希望在调用getInstance方法时才创建才好,这就是懒汉式单例模式,代码如下:
package main.singleton;
/**
* Created by leboop on 2018/11/27.
* final修饰:不可被其他类继承
*/
public class LazySingleton {
//定义实例,不直接初始化
//不能使用final修饰,也带来了一定的隐患,类的内部其他地方可以修改该实例
private static LazySingleton instance = null;
//构造方法私有化:不允许外部new实例化
private LazySingleton() {
}
//方法中实例化,没被实例化时才实例化
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
class LazySingletonMain {
public static void main(String[] args) {
LazySingleton singleton1 = LazySingleton.getInstance();
LazySingleton singleton2 = LazySingleton.getInstance();
//输出true
System.out.println(singleton1 == singleton2);
}
}
单例构造步骤:
1、构造方法私有化
和饿汉式单例模式构造相同。
2、static修饰getInstance成员方法
和饿汉式单例模式构造相同。
3、static修饰instance
和饿汉式单例模式构造相同。不能使用final修饰,这也给类的内部修改该实例带来隐患。
4、维度分析
(1)线程安全性
该方式是线程不安全的。类变量instance=null,因此当LazySingleton.class被初始化的时候instance并不会被实例化,会在getInstance中判断是否为空后再决定是否需要实例化。现在我们假设有两个线程:t1和t2,当t1调用getInstance()方法时,看到instance==null,未被实例化,那么t1将会执行该方法中的instance=new LazySingleton()进行初始化,但是假设t1执行到该方法前,t2线程也调用了getInstance方法,读到instance=null,因为此时实例尚未实例化,t2会进入if语句执行instance=new LazySingleton(),此次会创建两个instance实例,每个线程拥有一个。我们测试一下:
package main.singleton;
import java.util.HashSet;
import java.util.Set;
/**
* Created by leboop on 2018/11/27.
*/
public class LazySingletonTest {
//存放单例
private static Set<LazySingleton> set = new HashSet<>();
public static void main(String[] args) {
while (set.size() < 2) {
// 清空
set.clear();
//线程t1
new Thread(() -> {
LazySingleton singleton = LazySingleton.getInstance();
synchronized (set) {
set.add(singleton);
}
}
, "t1").start();
// 线程t2
new Thread(() -> {
LazySingleton singleton = LazySingleton.getInstance();
synchronized (set) {
set.add(singleton);
}
}, "t2").start();
}
System.out.println("set的大小:" + set.size());
for (LazySingleton singleton : set) {
System.out.println(singleton);
}
}
}
运行结果:
set的大小:2
main.singleton.LazySingleton@616831d4
main.singleton.LazySingleton@4ff782ab
(2)性能
性能比饿汉式高。不能保证单例,性能已经无从谈起。
(3)懒加载
该方式是懒加载。
懒汉式+synchronized
前面我们已经知道懒汉式是线程不安全的,下面通过添加synchronized使其变为线程安全的,代码如下:
package main.singleton;
/**
* Created by leboop on 2018/11/27.
*/
public final class SynchronizedLazySingleton {
//构造方法私有化:不允许外部new实例化
private SynchronizedLazySingleton() {
}
//定义实例,不直接初始化
private static SynchronizedLazySingleton singleton = null;
//方法中实例化
public static synchronized SynchronizedLazySingleton getInstance() {
if (singleton == null) {
singleton = new SynchronizedLazySingleton();
}
return singleton;
}
}
我们直接在懒汉式的getInstance方法上添加了同步关键字synchronized。
维度分析:
(1)线程安全性
该方式因添加了synchronized关键字保证了线程安全
(2)懒加载
该方式是懒加载
(2)性能
synchronized关键字天地的排他性导致了getInstance方法只能在同时时候被一个线程访问,性能低下。
Double Check Lock(DCL)
在《懒汉式+synchronized》中,直接在getInstance方法前通过暴力方式添加同步关键字synchronized使其线程安全,但是这种方法简单粗暴,如果该方法在实例化前还有很多代码逻辑需要处理,效率极其低下。所以我们可以细化synchronized锁,代码如下:
package main.singleton;
/**
* Created by leboop on 2018/11/27.
*/
public class RefineSynchronizedLazySingleton {
//构造方法私有化:不允许外部new实例化
private RefineSynchronizedLazySingleton() {
}
//定义实例,不直接初始化
private static volatile RefineSynchronizedLazySingleton singleton = null;
//方法中实例化
public static RefineSynchronizedLazySingleton getInstance() {
if (singleton == null) {
synchronized (RefineSynchronizedLazySingleton.class) {
if (singleton == null) {
singleton = new RefineSynchronizedLazySingleton();
}
}
}
return singleton;
}
}
这种方式在创建实例前,进行了两次检查(if ( singleton==null )),称之为double check lock,简称为DCL。该方式注意使用了volatile关键字。如果不使用不能保证单例。尚未找到例子证明。现只有原理说明。假设单例中还有一个私有成员变量a,定义如下:
private int a=1;
new关键字实例化时,指令有如下几个:
指令1:给准备实例化的实例分配一块内存 M,a先初始化为0;
指令2:在内存 M 上对成员变量赋值,此时a赋值为1;
指令3:然后 M 的地址赋值给 instance 变量。
但是实际上,经过编译器优化后可能会出现指令2和指令3重排序:
指令1:分配一块内存 M,a先初始化为0;
指令3:然后 M 的地址赋值给 instance 变量。
指令2:在内存 M 上对成员变量赋值,此时a赋值为1;
假设现在线程A执行完指令3,那么singleton对象指向的实例的成员变量a的值是0,此时线程B进行第一次check,发现singleton已经指向实例,不为空,所以直接返回。那么此时B线程拿到的是未完全实例化好的单例,a的值为0,接着线程A执行指令3完成实例化。如图:
引入volatile后,可以禁止有关singleton的指令重排序。
Holder方式
Holder方式是使用内部类的方式创建单例,代码如下:
package main.singleton;
/**
* Created by leboop on 2018/11/27.
*/
public final class HolderSingleton {
private HolderSingleton(){}
private static class Holder{
private static HolderSingleton holderSingleton=new HolderSingleton();
}
private static HolderSingleton getInstance(){
return Holder.holderSingleton;
}
}
该方式是一个线程安全,性能高,懒加载的方式,也是目前使用比较广的设计之一。
枚举方式
package main.singleton;
/**
* Created by leboop on 2018/11/27.
*/
public enum EnumSingleton {
SINGLETON;
EnumSingleton() {
System.out.println("SINGLETON将被初始化");
}
public static void method() {
System.out.println("method的方法被调用");
}
public static EnumSingleton getInstance() {
return SINGLETON;
}
}
三维分析:
(1)线程安全
该方式是线程安全的。
(2)性能
性能比较只是相对的,推荐使用该方式。
(3)懒加载
无法懒加载
注:当调用method()方法时,会主动初始化。
Holder+枚举方式
package main.singleton;
/**
* Created by leboop on 2018/11/27.
*/
public class HolderEnumSingleton {
//构造方法私有化
private HolderEnumSingleton() {
}
//使用enum枚举充当Holder
private enum EnumHolder {
INSTANCE;
private HolderEnumSingleton instance;
EnumHolder() {
this.instance = new HolderEnumSingleton();
}
private HolderEnumSingleton getSingleton() {
return instance;
}
}
//获取单例
public static HolderEnumSingleton getInstance() {
return EnumHolder.INSTANCE.getSingleton();
}
}
三维分析:
(1)线程安全
该方式是线程安全的
(2)性能
推荐使用。
(3)懒加载
该方式是懒加载的。
以上创建方式各个各的有点,需要根据实际情况选用,比如是否考虑线程安全,是否考虑性能等。