一、饿汉式(Eager Singleton)
package main.singleton;
/**
* Created by leboop on 2018/11/27.
* 声明为final,不可被继承
*/
public final class EagerSingleton {
//定义实例对象时直接初始化
private static EagerSingleton instance = new EagerSingleton();
//构造方法私有化,不允许外部new
private EagerSingleton() {
}
private static EagerSingleton getInstance() {
return instance;
}
}
1、final修饰类
final修饰的类不允许被其他类继承。
2、构造方法私有化
这一步是保证单例的基础,如果构造方法没有私有化,外部可以通过new的方式创建多个实例。
3、static修饰instance成员变量和getInstance成员方法
首先单例的外部不可以通过构造方法创建单例对象,只能通过EagerSingleton.getInstance的方式获取对象,所以getInstance成员方法需要声明为static,而static方法只能访问static成员变量,所以instance也需要使用static修饰。
4、维度分析
(1)线程安全
instance作为类变量在初始化的过程中会被收集进<clinit>()方法中,该方法能够保证同步,所以instance在多线程的情况下只能被实例化一次。
(2)性能
如果EagerSingleton中成员变量比较少,且占用的内存资源不多,该方式也未尝不可;但是,如果EagerSingleton中成员变量占用资源较多,那么该方式会有些不妥。instance被ClassLoader加载后可能很长时间才被使用,意味着instance实例开辟的堆内存空间会驻留更久的时间。
(3)懒加载
该方式无法进行懒加载。
二、懒汉式(Lazy Singleton)
package main.singleton;
/**
* Created by leboop on 2018/11/27.
* final修饰:不可被其他类继承
*/
public final class LazySingleton {
//构造方法私有化:不允许外部new实例化
private LazySingleton() {
}
//定义实例,不直接初始化
private static LazySingleton singleton = null;
//方法中实例化
public static LazySingleton getInstance() {
if (singleton == null) {
singleton = new LazySingleton();
}
return singleton;
}
}
1、final修饰类
同上。
2、构造方法私有化
同上。
3、static修饰instance成员变量和getInstance成员方法
同上。
4、维度分析
(1)线程安全
该方式是线程不安全。类变量instance=null,因此当LazySingleton.class被初始化的时候instance并不会被实例化,会在getInstance中判断是否为空后再决定是否需要实例化。现在我们假设有两个线程:线程1和线程2,当线程1调用getInstance()方法时,看到instance==null,未被实例化,那么线程1将会执行该方法中的instance=new LazySingleton(),但是假设线程1未执行实例化前,线程2也调用了getInstance方法,读到instance=null,也会进入if语句执行instance=new LazySingleton(),此次会创建两个instance实例,每个线程拥有一个。我们测试一下:
package main.singleton;
import java.util.concurrent.TimeUnit;
/**
* Created by leboop on 2018/11/27.
* final修饰:不可被其他类继承
*/
public final class LazySingleton {
//构造方法私有化:不允许外部new实例化
private LazySingleton() {
}
//定义实例,不直接初始化
private static LazySingleton singleton = null;
//方法中实例化
public static LazySingleton getInstance() {
if (singleton == null) {
System.out.println(Thread.currentThread().getName());
singleton = new LazySingleton();
}
return singleton;
}
}
package main.singleton;
import java.util.HashSet;
import java.util.Set;
/**
* Created by leboop on 2018/11/27.
*/
public class SingletonTest {
private static Set<LazySingleton> set = new HashSet<>();
public static void main(String[] args) {
new Thread(() ->
{
LazySingleton singleton=LazySingleton.getInstance();
synchronized (set) {
set.add(singleton);
}
}
).start();
new Thread(() ->
{
LazySingleton singleton=LazySingleton.getInstance();
synchronized (set) {
set.add(singleton);
}
}
).start();
while (Thread.activeCount() > 2) {
}
System.out.println("set的大小:" + set.size());
if (set.size() > 1) {
for(LazySingleton singleton:set){
System.out.println(singleton);
}
}
}
}
运行结果:
Thread-0
Thread-1
set的大小:2
main.singleton.LazySingleton@a3aa81
main.singleton.LazySingleton@1b2e63
(2)性能
性能比饿汉式高。不能保证单例,性能已经无从谈起。
(3)懒加载
该方式是懒加载。
三、懒汉式(Lazy Singleton)+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方法只能在同时时候被一个线程访问,性能低下。
四、改进懒汉式(Lazy Singleton)+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 SynchronizedLazySingleton getInstance() {
if (singleton == null) {
synchronized (SynchronizedLazySingleton.class) {
if (singleton == null) {
singleton = new SynchronizedLazySingleton();
}
}
}
return singleton;
}
}
刚方式使用同步代码块改进了第三种方式:懒汉式(Lazy Singleton)+synchronized。当两个线程发现singleton ==null,只要一个线程可以进入同步代码块,完成对instance的实例化,等线程1同步代码块执行完毕,线程2进入同步代码块,发现singleton已经实例化,无需进行任何操作。以后对getInstance()方法的访问就不需要进入同步代码块,较第三种方式性能有提高。
五、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)懒加载
该方式是懒加载的。
参见《Java高并发编程详解》