单例模式(Singleton Pattren)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。在J2EE保准中的ServerletContext、ServerletContextConfig等、Spring框架中的ApplicationContext、数据库的连接池等都是单例模式。
package nju.java.pattern.singleton_pattern;
/**
* 饿汉式单例模式
* 在类加载的时候就立即初始化,并且创建单例对象。它绝对线程安全,在线程还没出现以前就实例化了,不可能存在 访问安全问题
* 优点:没有加任何锁、执行效率比较高,用户体验比懒汉式单例模式更好。
* 缺点: 类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能“占着茅坑不拉屎“
*/
public class HungrySingleton {
private static final HungrySingleton hunrySingleton=new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hunrySingleton;
}
}
//也可以下面这样写
/*
public class HungryStaticSingleton{
private static final HungrySingleton hungrySingleton;
static {
hungrySingleton=new HungryStaticSingleton();
}
public static HungryStaticSingleton getInstance(){
return hungrySingleton;
}
}
*/
懒汉式单例模式的特点是:被外部类调用的时候内部类才会加载。
所以一般是用synchronized来实现,当然也可以其他方式,比如静态内部类,看如下代码:
package nju.java.pattern.singleton_pattern;
//懒汉单例模式在外部需要使用的时候才进行实例化
public class LazySimpleSingleton {
private LazySimpleSingleton(){}
//静态块,公共内存区域
private static LazySimpleSingleton lazy=null;
//第一版本 这个构建方法在多线程情况下存在危险 因为后执行的线程会覆盖前面的 导致单例并不是单例
/*
public static LazySimpleSingleton getInstance(){
if (lazy==null){
lazy=new LazySimpleSingleton();
}
return lazy;
}
*/
//第二版本 加上synchronized之后 线程安全问题解决了。但是,用synchronized加锁时,在线程数量较多的情况下,
// 如果CPU分配压力上升,则会导致大批线程阻塞,从而导致程序性能大幅下降。
/*
public static synchronized LazySimpleSingleton getInstance(){
if (lazy==null){
lazy=new LazySimpleSingleton();
}
return lazy;
}
*/
//下面这种方法 兼顾了线程安全又能提升程序性能
//当然还是会遇到synchronized 会遇到上锁 可以参考LazyInnerClassSingleton
public static LazySimpleSingleton getInstance(){
if (lazy==null){
synchronized (LazySimpleSingleton.class){
if (lazy==null){
lazy=new LazySimpleSingleton();
//1.分配内存给这个对象
//2.初始化对象
//3.设置lazy指向刚分配的内存地址
}
}
}
return lazy;
}
}
上面这是synchronized的实现方法,下面是静态内部类的实现方法:
package nju.java.pattern.singleton_pattern;
public class LazyInnerClassSingleton {
//使用 LazyinnerClassGeneral 的时候,默认会先初始化内部
//如果没使用,则内部类是不加载的
//原先版本
// private LazyInnerClassSingleton(){}
//每一个关键字都不是多余的,static是为了使单例的空间分享,保证这个方法不会重写、重载
public static final LazyInnerClassSingleton getInstance(){
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY=new LazyInnerClassSingleton();
}
//上面版本有个问题 就是如果调用LazyInnerClassSingletonTest 是会出现问题的
//所以下面有个书中所说的无敌强的完美的版本 实现单例模式
private LazyInnerClassSingleton(){
if (LazyHolder.LAZY!=null){
throw new RuntimeException("不允许创建多个实例");
}
}
}
注册式单例模式分为两种: 1.枚举式单例模式 2.容器式单例
package nju.java.pattern.singleton_pattern;
/**
* 枚举式单例(属于注册式单例)
* 这种单例模式序列化无法破坏
* 反射也不能破坏 会抛异常"Cannot reflectively create enum objects" 即不能用反射来创建枚举类型
*/
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData(){
return data;
}
public void setData(Object data){
this.data=data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
package nju.java.pattern.singleton_pattern;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 容器式单例(属于注册式单例)
* Spring中也会用到这样的实现方法
*/
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map ioc=new ConcurrentHashMap();
public static Object getBean(String className){
synchronized (ioc){
if (!ioc.containsKey(className)){
Object obj=null;
try{
obj=Class.forName(className).newInstance();
ioc.put(className,obj);
}catch (Exception e){
e.printStackTrace();
}
return obj;
}else {
return ioc.get(className);
}
}
}
}
有些单例模式的实现,在反射来调用其构造方法时,会破坏单例,比如LazyInnerClassSingleton的最初版本:
public class LazyInnerClassSingleton {
//使用 LazyinnerClassGeneral 的时候,默认会先初始化内部
//如果没使用,则内部类是不加载的
private LazyInnerClassSingleton(){}
//每一个关键字都不是多余的,static是为了使单例的空间分享,保证这个方法不会重写、重载
public static final LazyInnerClassSingleton getInstance(){
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY=new LazyInnerClassSingleton();
}
}
上面版本没有之前写的好,因为在如下利用反射的测试中,会破坏单例。
package nju.java.pattern.singleton_pattern;
import java.lang.reflect.Constructor;
public class LazyInnerClassSingletonTest {
//通过反射破坏单例
public static void main(String[] args) {
try {
//在很无聊的情况下,进行破坏
Class> clazz = LazyInnerClassSingleton.class;
//通过反射获取私有的构造方法
Constructor c = clazz.getDeclaredConstructor(null);
//强制访问
c.setAccessible(true);
//暴力初始化
Object o1=c.newInstance();
//调用了两次构造方法,相当于"new"了两次,犯了原则性错误
Object o2=c.newInstance();
System.out.println(o1==o2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
上述的调用会直接破坏单例模式,最后会输出false。
package nju.java.pattern.singleton_pattern;
//反序列化导致破坏单例模式
public class SeriableSingleton implements Serializable {
//序列化就是把内存中的状态通过转换成字节码的形式
//从而转换一 I/O 写入其他地方(可以是磁盘、网络 I/O
// 内存中的状态会永久保存
// 反序列化就是将已经持久化的字节码内容转换为 I/O
// 通过 I/O 流的读取,进而将读取的内容转换为 Java 对象
// 在转换过程中会重新创建对象 new
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
}
package nju.java.pattern.singleton_pattern;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SeriableSingletonTest {
//通过下面的测试之后 会发现两次创建的对象 s1 s2不一致 内存地址不同
//如何解决? 在SeriableSingleton中加上readResolve就行了
public static void main(String[] args) {
SeriableSingleton sl = null;
SeriableSingleton s2 = SeriableSingleton.getInstance();
FileOutputStream fos = null;
try{
fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
sl = (SeriableSingleton) ois.readObject();
ois.close();
System.out.println(sl);
System.out.println(s2);
System.out.println(sl == s2);
}catch(Exception e){
e.printStackTrace();
}
}
}
测试会发现两个对象不一致。
如何解决呢? 添加一个readResolve() 方法即可。
package nju.java.pattern.singleton_pattern;
import java.io.Serializable;
//反序列化导致破坏单例模式
public class SeriableSingleton implements Serializable {
//序列化就是把内存中的状态通过转换成字节码的形式
//从而转换一 I/O 写入其他地方(可以是磁盘、网络 I/O
// 内存中的状态会永久保存
// 反序列化就是将已经持久化的字节码内容转换为 I/O
// 通过 I/O 流的读取,进而将读取的内容转换为 Java 对象
// 在转换过程中会重新创建对象 new
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
public Object readResolve(){
return INSTANCE;
}
}
然后在测试一下:
为什么呢?看一下jdk源码就知道,添加了readResolve()方法后,实际上还是实例化了两次,但是新创建的对象没有被返回而已。如果创建对象的动作发生频率加快,就意味着内存分配开销也会随之增大。上面的注册式单例就起到作用了。
package nju.java.pattern.singleton_pattern;
/**
* 线程单例实现ThreadLocal
* ThreadLocal 保证其创建的对象是全局唯一的,
* 但是能保证在单个线程中是唯一的,天生是线程安全的
*/
public class ThreadLocalSingleton {
public static final ThreadLocal threadLocalInstance=
new ThreadLocal(){
@Override
protected ThreadLocalSingleton initialValue(){
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton(){}
public static ThreadLocalSingleton getInstance(){
return threadLocalInstance.get();
}
}
代码位置:https://github.com/YellowDii/MySpring.git
参考书籍:《Spring5核心原理与30个类手写实现》