什么是单例模式
一个对象在应用程序中只存在一个。
单例实现原理
以java为例,私有构造器,一个私有的静态的本身实例,一个对外开放获取本身实例的静态公共方法。
多种不同的方式实现单例
饿汉式
在使用这个类时就初始化这个对象。
package com.lxlyq.singlePattern;
/**
* 饿汉式单例
*/
public class HungrySingle {
// 私有对象
private static HungrySingle hungrySingle = new HungrySingle();
// 私有构造,防止外部new
private HungrySingle(){}
// 外部调用
public static HungrySingle getHungrySingle() {
return hungrySingle;
}
}
缺点:可能没有使用这个对象,但应用程序中也存在这个对象,浪费内存。
懒汉式
只有获取这个实例时才初始化对象。
package com.lxlyq.singlePattern;
/**
* 懒汉式
*/
public class LazySingle {
// 私有对象 未初始化
private static LazySingle lazySingle;
// 私有构造 防止外部通过 new 创建
private LazySingle() { }
// 获取实例
public static LazySingle getInstance() {
// 不存在则创建
if (lazySingle == null) {
lazySingle = new LazySingle();
}
return lazySingle;
}
}
优点:弥补了饿汉式的不足。
缺点:当存在多线程的时候将存在线程安全问题,两个线程同时进入if判断语句中,将会存在两个对象。
懒汉式同步方法锁
package com.lxlyq.singlePattern;
/**
* 懒汉式(同步方法)
*/
public class LazySynchronizedMethodSingle {
// 私有对象 未初始化
private static LazySynchronizedMethodSingle lazySingle;
// 私有构造 防止外部通过 new 创建
private LazySynchronizedMethodSingle() { }
// 获取实例 加上同步锁 防止多线程创建多个实例
public static synchronized LazySynchronizedMethodSingle getInstance() {
// 不存在则创建
if (lazySingle == null) {
lazySingle = new LazySynchronizedMethodSingle();
}
return lazySingle;
}
}
优点:当获取时才创建实例,不存在线程安全问题。
缺点:每次获取实例时都需要加锁,效率极低。
懒汉式同步块锁
package com.lxlyq.singlePattern;
/**
* 懒汉式同步块
*/
public class LazySynchronizedBlockSingle {
// 私有对象 未初始化
private static LazySynchronizedBlockSingle lazySingle;
// 私有构造 防止外部通过 new 创建
private LazySynchronizedBlockSingle() { }
// 获取实例 加上同步块防止多线程创建多个实例
public static LazySynchronizedBlockSingle getInstance() {
synchronized (LazySynchronizedBlockSingle.class) {
// 不存在则创建
if (lazySingle == null) {
lazySingle = new LazySynchronizedBlockSingle();
}
}
return lazySingle;
}
}
与同步方法锁一样,效率也差不多,每次都需要加锁。需要改进如下
懒汉式双重校验同步块锁
package com.lxlyq.singlePattern;
/**
* 懒汉式双重校验同步块
*/
public class LazyDoubleCheckSynchronizedBlockSingle {
// 私有对象 未初始化
private static LazyDoubleCheckSynchronizedBlockSingle lazySingle;
// 私有构造 防止外部通过 new 创建
private LazyDoubleCheckSynchronizedBlockSingle() { }
// 获取实例 加上同步块防止多线程创建多个实例
public static LazyDoubleCheckSynchronizedBlockSingle getInstance() {
if (lazySingle == null) {
// 第一次加锁,以后都不会再进入
synchronized (LazyDoubleCheckSynchronizedBlockSingle.class) {
// 不存在则创建
if (lazySingle == null) {
lazySingle = new LazyDoubleCheckSynchronizedBlockSingle();
}
}
}
return lazySingle;
}
}
优点:只有获取对象时才被创建。不存在线程安全问题。因为双重校验,只有当对象未被创建前才会被加锁,效率满足要求。
缺点:不符合审美(瞎扯)。
以上的真的都是单例了吗
// 通过反射破坏
package com.lxlyq.singlePattern;
import java.lang.reflect.Constructor;
public class SingleDemo {
public static void main(String[] args) throws Exception {
// 通过懒汉式双重校验获取实例
LazyDoubleCheckSynchronizedBlockSingle single1 = LazyDoubleCheckSynchronizedBlockSingle.getInstance();
System.out.println(single1);
// 通过懒汉式双重校验获取实例
LazyDoubleCheckSynchronizedBlockSingle single2 = LazyDoubleCheckSynchronizedBlockSingle.getInstance();
System.out.println(single2);
// 通过反射创建对象
Class cls = LazyDoubleCheckSynchronizedBlockSingle.class;
Constructor<?> cs = cls.getDeclaredConstructor();
cs.setAccessible(true);
LazyDoubleCheckSynchronizedBlockSingle single3 = (LazyDoubleCheckSynchronizedBlockSingle) cs.newInstance();
System.out.println(single3);
// out
// com.lxlyq.singlePattern.LazyDoubleCheckSynchronizedBlockSingle@4554617c
// com.lxlyq.singlePattern.LazyDoubleCheckSynchronizedBlockSingle@4554617c
// com.lxlyq.singlePattern.LazyDoubleCheckSynchronizedBlockSingle@74a14482
}
}
之前所有的单例创建方式都存在通过反射破坏单例问题。
解决单例破坏问题
package com.lxlyq.singlePattern;
/**
* 禁止通过反射创建单例
*/
public class BanReflexCreateSinge {
// 采用饿汉式,但是客户端一样可以通过反射创建
private static BanReflexCreateSinge banReflexCreateSinge = new BanReflexCreateSinge();
// 防止反射
private BanReflexCreateSinge(){
if (banReflexCreateSinge != null) {
throw new RuntimeException("禁止一切方法破坏单例");
}
}
// 外部调用
public static BanReflexCreateSinge getInstance() {
return banReflexCreateSinge;
}
}
通过这种反射,当系统中存在时就会抛出异常。
有一种通过枚举方式创建单例,枚举的底层也是这个原理。
这里没有介绍通过枚举创建单例,是我觉得枚举没有类那么灵活,尽管枚举是创建单例的最好方式,但不是最好的方案。
静态内部内实现单例
package com.lxlyq.singlePattern;
/**
* 内部类创建单例
*/
public class LazyInnerClassSingle {
// 防止外部调用
private LazyInnerClassSingle(){}
// 静态内部类
private static class LazyInnerClass{
private static LazyInnerClassSingle lazyInnerClassSingle = new LazyInnerClassSingle();
}
// 获取实例
public static LazyInnerClassSingle getInstance() {
return LazyInnerClass.lazyInnerClassSingle;
}
}
这种方式,没有加锁,但也是懒汉式,代码简洁,推荐使用。
总结
单例优点:
- 系统中只存在一个,减少内存开支,当一个对象需要被频繁创建销毁时,把它作为单例效果将极其明显。
- 系统多次需要使用一个对象时,把他设为单例可以减少系统开支。(如读取的配置信息)。
- 单例可以防止资源被多重占用,如读写文件。
单例缺点:
- 不适用于变化的对象
- 一般单例的职责都过重,违背了'单一原则'(一个类负责一件事)
- 单例没有抽象层,难以扩展。