JAVA设计模式之单例模式(详解篇)

目录

单例模式含义

单例模式特征

单例模式的常见写法

单例模式的优点

单例模式的缺点

饿汉式单例

懒汉式单例

反射破坏单例

序列化破坏单例

注册式单例

ThreadLocal 线程单例


单例模式含义

  • 单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
 

单例模式特征

  • 一个类只允许产生一个实例化对象。
  • 单例类构造方法私有化,不允许外部创建对象。
  • 单例类向外提供静态方法,调用方法返回内部创建的实例化对象。

 

单例模式的常见写法

  • 饿汉式单例
  • 懒汉式单例(双重检查锁、静态内部类
  • 注册式单例(容器缓存、枚举登记
  • ThreadLocal 线程单例

 

单例模式的优点

  • 在内存中只有一个实例,减少了内存开销。
  • 可以避免对资源的多重占用。
  • 设置全局访问点,严格控制访问。

 

单例模式的缺点

  • 没有接口,扩展困难。
  • 如果要扩展单例对象,只有修改代码,没有其他途径。

 

饿汉式单例

含义:

  • 在类加载的时候就立即初始化,并且创建单例对象

优点:

  • 绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题。
  • 没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好

缺点:

  • 类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅坑不拉屎

举例说明:

public class HungrySingleton {

	//类加载顺序:先静态、后动态,先属性、后方法,先上后下
	//私有的静态的全局变量
	private static HungrySingleton hungrySingleton = new HungrySingleton();
	
	//类构造器私有
	private HungrySingleton() {};
	
	//提供一个全局访问点
	public static HungrySingleton getInstence() {
		return hungrySingleton;
	}
}

另外一种写法,利用静态代码块的机制:

public class HungryStaticPattern {
	//私有的静态的全局变量
	private static HungryStaticPattern hHungryStaticPattern;
	
	static {
		hHungryStaticPattern = new HungryStaticPattern();
	}
	//类构造器私有
	private HungryStaticPattern() {};
	
	//提供一个全局访问点
	public static HungryStaticPattern getInstence() {
		return hHungryStaticPattern;
	}
}

懒汉式单例

含义:

  • 被外部类调用时才创建实例,线程不安全

优点:

  • 节省内存

缺点:

  • 延时创建,需要时才创建实例效率慢,线程不安全

举例说明:

1、简单懒汉模式:

LazySimpleSingleton类:

public class LazySimpleSingleton {
	//私有的静态的全局变量
	private static LazySimpleSingleton lazySimpleSingleton = null;

	//类构造器私有,防止反射利用构造方法创建实例
	private LazySimpleSingleton() {};
	
	/**
	 * 	提供一个全局访问点
	 * @return
	 */
	public static LazySimpleSingleton getInstence() {
		if(lazySimpleSingleton == null) {
			lazySimpleSingleton = new LazySimpleSingleton();
		}
		return lazySimpleSingleton;
	}
}

然后写一个线程类 ExectorThread 类:

//线程类,在线程中获取单例对象
public class ExectorThread implements Runnable {

	@Override
	public void run() {
		// TODO Auto-generated method stub
		LazySimpleSingleton lazySimpleSingleton = LazySimpleSingleton.getInstence();
	}

}

测试代码:

public static void main(String[] args) {
	Thread t1 = new Thread(new ExectorThread());
	Thread t2 = new Thread(new ExectorThread());
		
	t1.start();
	t2.start();
		
	System.out.println("End");
		
}

运行结果:

 
一定几率出现创建两个不同结果的情况,意味着上面的单例存在线程安全隐患。现在用调试运行( 用线程模式调试,手动控制线程的执行顺序来跟踪内存的变化状态)可以发现,有时,得到的运行结果可能是相同的两个对象,实际上是被后面执行的线程覆盖了,我们看到了一个假象,线程安全隐患依旧存在。
 
给 getInstance()加上 synchronized 关键字,是这个方法变成线程同步方法:
public synchronized static LazySimpleSingleton getInstence1() {
	if(lazySimpleSingleton == null) {
		lazySimpleSingleton = new LazySimpleSingleton();
	}
	return lazySimpleSingleton;
}

​ 上述方式虽然解决了线程安全问题,但是整个方法都是锁定的,在线程数量比较多情况下,如果 CPU 分配压力上升,会导致大批量线程出现阻塞,从而导致程序运行性能大幅下降,所以我们使用方法内加锁的方式解决提高性能

 

2、双重检查锁模式

public class LazySimpleSingleton {
	//私有的静态的全局变量
	private static volatile LazySimpleSingleton lazySimpleSingleton = null;

	//类构造器私有,防止反射利用构造方法创建实例
	private LazySimpleSingleton() {};
	
	/**
	 * 	提供一个全局访问点
	 * @return
	 */
	public static LazySimpleSingleton getInstence2() {
		if(lazySimpleSingleton == null) {
			synchronized(LazySimpleSingleton.class) {
				if(lazySimpleSingleton == null) {
					lazySimpleSingleton = new LazySimpleSingleton();
					//CPU执行时候会转换成JVM指令执行
					//1.分配内存给这个对象 //2.初始化对象 //3.将初始化好的对象和内存地址建立关联,赋值 //4.用户初次访问
					//这种方式,在cpu中3步和4步有可能进行指令重排序。有可能用户获取的对象是空的。
					// 那么我们可以使用volatile关键字,作为内存屏障,保证对象的可见性来保证我们对象的单一。
				}
			}
		}
		return lazySimpleSingleton;
	}
}

这样线程也是安全的而且效率比上一种要好,但是,用到 synchronized 关键字,总归是要上锁,对程序性能还是存在一定影响的。

3、静态内部类模式

从类初始化角度来考虑利用静态内部类在调用的时候等到外部方法调用时才执行,巧妙的利用了内部类的特性,jvm底层逻辑来完美的避免了线程安全问题

/**
 *	 静态内部类实现
 * @author 15616
 *
 */
public class LazyInnerClassSingleton {
	
	//类构造器私有,防止反射利用构造方法创建实例
	private LazyInnerClassSingleton() {}
	
	/***
	 *      默认使用 LazyInnerClassSingleton 的时候,会先初始化内部类 ,如果没使用的话,内部类是不加载的
	 * 	优点:内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题
	 * 	每一个关键字都不是多余的 static是为了使单例的空间共享保证这个方法不会被重写、重载
	 * @return
	 */
	private static final LazyInnerClassSingleton getInatence() {
		//在返回结果以前,一定会先加载内部类
		return LazyHodler.LAZY;
	}
	
	//默认不加载,在被外部调用的方法之前加载
	private static class LazyHodler{
		private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
	}
	
}
这种形式兼顾饿汉式的内存浪费,也兼顾 synchronized 性能问题。内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题。

 

反射破坏单例

前面单例模式的构造方法除了加上 private 以外,没有做任何处理。如果我们使用反射来调用其构造方法,然后,再调用 getInstance()方法,应该就会 两个不同的实例。

下面我们通过反射调用单例模式,以LazyInnerClassSingleton为例

测试代码
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) {
		// TODO: handle exception
		e.printStackTrace();
	}
}
运行结果

显然,是创建了两个不同的实例。现在,我们在其构造方法中做一些限制,一旦出现多次重复创建,则直接抛出异常。来看优化后的代码
 
public class LazyInnerClassSingleton {
	
	//类构造器私有,防止反射利用构造方法创建实例
	private LazyInnerClassSingleton() {
		if(LazyHodler.LAZY != null) {
			throw new RuntimeException("不允许创建多个实例");
		}
	}
	
	/***
	 *      默认使用 LazyInnerClassSingleton 的时候,会先初始化内部类 ,如果没使用的话,内部类是不加载的
	 * 	优点:内部类一定是要在方 法调用之前初始化,巧妙地避免了线程安全问题
	 * 	每一个关键字都不是多余的 static是为了使单例的空间共享保证这个方法不会被重写、重载
	 * @return
	 */
	private static final LazyInnerClassSingleton getInatence() {
		//在返回结果以前,一定会先加载内部类
		return LazyHodler.LAZY;
	}
	
	//默认不加载,在被外部调用的方法之前加载
	private static class LazyHodler{
		private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
	}
	
}
再运行测试代码,得到以下结果

在构造方法里面加判断后,再通过反射强制创建就出抛出我们刚设定的异常。 至此,已成功防止反射破坏单例

 

序列化破坏单例

当我们将一个单例对象创建好,有时候需要将对象序列化然后写入到磁盘,下次使用时再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存,即重新创建。那如果序列化的目标的对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例,来看一段代码:
 
public class SerializableSingleton implements Serializable{
	
	//私有的静态的全局变量
	private static SerializableSingleton serializableSingleton = new SerializableSingleton();
	
	//类构造器私有
	private SerializableSingleton() {
		if(serializableSingleton != null) {
			throw new RuntimeException("不允许创建多个实例");
		}
	};
	
	
	//提供一个全局访问点
	public static SerializableSingleton getInstence() {
		return serializableSingleton;
	}
}

测试代码:

@SuppressWarnings("resource")
	public static void main(String[] args) {
		SerializableSingleton s1 = null;
		
		SerializableSingleton s2 = SerializableSingleton.getInstence();
		
		
		try {
			//序列化对象
			FileOutputStream fos = new FileOutputStream("SerializableSingleton.obj");
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject(s2);
			oos.flush();
			oos.close();
			
			//反序列化对象
			FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
			ObjectInputStream ois = new ObjectInputStream(fis);
			s1 = (SerializableSingleton) ois.readObject();
			ois.close();
			
			
			System.out.println(s1); 
			System.out.println(s2); 
			System.out.println(s1 == s2);
			
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		
	}

运行结果

从运行结果中,可以看出,反序列化后的对象和手动创建的对象是不一致的,实例化了两次,违背了单例的设计初衷

优化解决方案,在单例类中添加readResolve()方法即可,在来看下单例类

public class SerializableSingleton implements Serializable{
	
	//私有的静态的全局变量
	private static SerializableSingleton serializableSingleton = new SerializableSingleton();
	
	//类构造器私有
	private SerializableSingleton() {
		if(serializableSingleton != null) {
			throw new RuntimeException("不允许创建多个实例");
		}
	};
	
	/**
	 *	 防止序列化破坏单例
	 * @return
	 */
	private Object readResolve() {
		return serializableSingleton;
	}
	
	//提供一个全局访问点
	public static SerializableSingleton getInstence() {
		return serializableSingleton;
	}
}

然后再来运行看下结果

可以看到已经避免了序列化破坏单例,下面来看下为什么要这么写呢,其实在JDK源码中有详细说明,反序列化调用readObject() 的方法里面,调用了readObject0()返回的Object,而这个readObject0()就对有没有resolve方法做了check,如果有就,就判读是否构造方法空。

我 们 进 入 ObjectInputStream 类的 readObject()方法,代码如下
public final Object readObject()throws IOException, ClassNotFoundException {
    if (enableOverride) {
        return readObjectOverride();
    }
 
    // if nested read, passHandle contains handle of enclosing object
    int outerHandle = passHandle;
    try {
        Object obj = readObject0(false);
        handles.markDependency(outerHandle, passHandle);
        ClassNotFoundException ex = handles.lookupException(passHandle);
        if (ex != null) {
            throw ex;
        }
        if (depth == 0) {
            vlist.doCallbacks();
        }
        return obj;
    } finally {
        passHandle = outerHandle;
        if (closed && depth == 0) {
            clear();
        }
    }
}
我们发现在readObject中又调用了我们重写的readObject0()方法。进入readObject0()方法,代码如下
private Object readObject0(boolean unshared) throws IOException {
         
    ...
    case TC_OBJECT:
        return checkResolve(readOrdinaryObject(unshared));
    ...
         
}

我们看到 TC_OBJECTD 中判断,调用了 ObjectInputStream 的 readOrdinaryObject() 方法,我们继续进入看源码

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }
 
        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();
 
        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }
 
        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
        ...
        return obj;
    }
发现调用了 ObjectStreamClass 的 isInstantiable()方法,而 isInstantiable()里面的代码如下
boolean isInstantiable() {
    requireInitialized();
    return (cons != null);
}
代码非常简单,就是判断一下构造方法是否为空,构造方法不为空就返回 true。意味着,只要有无参构造方法就会实例化。这时候,其实还没有找到为什么加上 readResolve()方法就避免了单例被破坏的真正原因。我再回到 ObjectInputStream 的readOrdinaryObject()方法继续往下
private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }
 
        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();
 
        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }
 
        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
        ...
        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }
 
        return obj;
    }

判断无参构造方法是否存在之后,又调用了 hasReadResolveMethod()方法,进入看源码

boolean hasReadResolveMethod() {
    requireInitialized();
    return (readResolveMethod != null);
}
逻辑非常简单,就是判断 readResolveMethod 是否为空,不为空就返回 true。那么 readResolveMethod 是在哪里赋值的呢?通过全局查找找到了赋值代码在私有方法ObjectStreamClass()方法中给 readResolveMethod 进行赋值,来看代码
readResolveMethod = getInheritableMethod(
      cl, "readResolve", null, Object.class);

上面的逻辑其实就是通过反射找到一个无参的 readResolve()方法,并且保存下来。现在再回到 ObjectInputStream 的readOrdinaryObject() 方法继续往下看,如 果 readResolve()存在则调用 invokeReadResolve()方法,来看代码

Object invokeReadResolve(Object obj)
        throws IOException, UnsupportedOperationException
    {
        requireInitialized();
        if (readResolveMethod != null) {
            try {
                return readResolveMethod.invoke(obj, (Object[]) null);
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ObjectStreamException) {
                    throw (ObjectStreamException) th;
                } else {
                    throwMiscException(th);
                    throw new InternalError(th);  // never reached
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }
我们可以看到在 invokeReadResolve()方法中用反射调用了 readResolveMethod 方法。通过 JDK 源码分析我们可以看出,虽然增加 readResolve()方法返回实例,解决了单例被破坏的问题。但是,我们通过分析源码以及调试,我们可以看到实际上实例化了两 次,只不过新创建的对象没有被返回而已。那如果,创建对象的动作发生频率增大,就意味着内存分配开销也就随之增大
 
 

注册式单例

含义:

  • 注册式单例又称为登记式单例就是将每一个实例都缓存到统一的容器中,使用唯一标识获取实例。

举例说明:

注册式单例有两种写法:一种为容器缓存,一种为枚举登记

1、枚举式单例

创建 EnumSingleton 类

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;
	}
	
}

测试代码

public class EnumSingletonTest {

	public static void main(String[] args) {
		EnumSingleton e1 = null;
		
		EnumSingleton e2 = EnumSingleton.getInstance();
		e2.setData(new Object());
		try {
			//序列化对象
			FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject(e2);
			oos.flush();
			oos.close();
			
			//反序列化对象
			FileInputStream fis = new FileInputStream("EnumSingleton.obj");
			ObjectInputStream ois = new ObjectInputStream(fis);
			e1 = (EnumSingleton) ois.readObject();
			ois.close();
			
			
			System.out.println(e1.getData()); 
			System.out.println(e2.getData()); 
			System.out.println(e1.getData() == e2.getData());
			
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		
	}

}
运行结果

没有做任何处理,我们发现运行结果和我们预期的一样。枚举式单例如此神奇我们通过分析源码来进行分析。

先下载一个java反编译工具jad(下载地址:https://varaneckas.com/jad/),下载后解压后配置好环境变量就可以使用命令行调用了(不会下载的童鞋看这里)

先找到项目中编译后的EnumSingleton.class文件路径选择复制,然后打开cmd命令行,粘贴刚刚复制的文件路径进入其目录,然后输入命令 jad和空格再输入EnumSingleton.class文件全名包括.class后缀最后会在该目录下就会生成一个EnumSingleton.jad文件

打开 EnumSingleton.jad文件看下反编译后的代码

 

因此我们得知,枚举式单例在静态代码块中就给 INSTANCE 进行了赋值,是饿汉式单例的实现。序列化能不能破坏枚举式单例,其实在JDK源码中也有体现,我们继续回到ObjectInputStream 的 readObject0()方法中
private Object readObject0(boolean unshared) throws IOException {
    ...
    case TC_ENUM:
         return checkResolve(readEnum(unshared));
    ...
}
我们看到在 readObject0()中调用了 readEnum()方法,来看 readEnum()中代码实现
private Enum<?> readEnum(boolean unshared) throws IOException {
        if (bin.readByte() != TC_ENUM) {
            throw new InternalError();
        }
 
        ObjectStreamClass desc = readClassDesc(false);
        if (!desc.isEnum()) {
            throw new InvalidClassException("non-enum class: " + desc);
        }
 
        int enumHandle = handles.assign(unshared ? unsharedMarker : null);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(enumHandle, resolveEx);
        }
 
        String name = readString(false);
        Enum<?> result = null;
        Class<?> cl = desc.forClass();
        if (cl != null) {
            try {
                @SuppressWarnings("unchecked")
                Enum<?> en = Enum.valueOf((Class)cl, name);
                result = en;
            } catch (IllegalArgumentException ex) {
                throw (IOException) new InvalidObjectException(
                    "enum constant " + name + " does not exist in " +
                    cl).initCause(ex);
            }
            if (!unshared) {
                handles.setObject(enumHandle, result);
            }
        }
 
        handles.finish(enumHandle);
        passHandle = enumHandle;
        return result;
    }
我们发现枚举类型其实通过类名和 Class 对象类找到一个唯一的枚举对象。因此,枚举对象不可能被类加载器加载多次。
 
接下来,我们在测试一下枚举类单例会不会被发射破坏。
测试代码
public static void reflectTest() {
		try {
			Class clazz = EnumSingleton.class;
			Constructor constructor = clazz.getDeclaredConstructor();
			EnumSingleton singleton = (EnumSingleton) constructor.newInstance();
			System.out.println(singleton);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

运行结果

报的是 java.lang.NoSuchMethodException 异常,意思是没找到无参的构造方法。这时候,我们打开 java.lang.Enum 的源码代码,查看它的构造方法,只有一个 protected的构造方法,代码如下
protected Enum(String name, int ordinal) {
    this.name = name;
    this.ordinal = ordinal;
}

那我们就传入两个参数,再试一次

//反射测试
	public static void reflectTest2() {
		try {
			Class clazz = EnumSingleton.class;
			Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
			constructor.setAccessible(true);//强制访问
			EnumSingleton singleton = (EnumSingleton) constructor.newInstance("CHINA", 666);
			System.out.println(singleton);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
运行结果

错误信息是 Cannot reflectively create enum objects,意思是不能用反射来创建枚举类型。我们来看看 JDK 源码,进入 Constructor 的 newInstance()方法
@CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }
在 newInstance()方法中可以看出,如果修饰符是 Modifier.ENUM 枚举类型, 直接抛出异常。因此,我们可以肯定枚举式单例模式不可能被反射破坏 。由于 JDK 枚举的语法特殊性,以及反射也为枚举保驾护航,让枚举式单例成为一种比较优雅的实现。

2、容器式单例

容器式写法适用于创建实例非常多的情况,便于管理。但是,是非线程安全的。

创建 ContainerSingleton 类
public class ContainerSingleton {
	
	//私有化构造方法
	private ContainerSingleton() {}
	
	//私有容器
	private static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();
	
	//全局访问获取实例
	public static Object getInstance(String className) {
		//使用synchronized关键字包裹防止线程安全问题
		synchronized (ioc) {
			//判断获取对象是否存在,如果存在就获取不存在则添加
			if(!ioc.containsKey(className)) {
				Object obj= null;
				try {
					//通过反射获取此实例(此处使用了简单工厂模式)
					obj = Class.forName(className).getInterfaces();
					ioc.put(className, obj);
				} catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				}
				return obj;
			}else {
				return ioc.get(className);
			}
		}
	}
	
}

虽然ConcurrentHashMap是线程安全的,只代表map中的线程安全,但是放入map的过程不是线程安全的,所以需要加synchronized关键字。容器式单例更适合创建多个单例模式。防反射破坏和和防序列化破坏前面懒汉式单例有详细说明,这个就不在测试了。

接下来顺便看一下spring中的容器式单例的实现,例如:AbstractAutowireCapableBeanFactory里面就是容器式单例。

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
        implements AutowireCapableBeanFactory {
        ...
        /** Cache of unfinished FactoryBean instances: FactoryBean name --> BeanWrapper */
    private final Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>(16);
        ...
 
      private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
        synchronized (getSingletonMutex()) {
            BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
            if (bw != null) {
                return (FactoryBean<?>) bw.getWrappedInstance();
            }
            if (isSingletonCurrentlyInCreation(beanName) ||
                    (mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) {
                return null;
            }
 
            Object instance = null;
            try {
                // Mark this bean as currently in creation, even if just partially.
                beforeSingletonCreation(beanName);
                // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
                instance = resolveBeforeInstantiation(beanName, mbd);
                if (instance == null) {
                    bw = createBeanInstance(beanName, mbd, null);
                    instance = bw.getWrappedInstance();
                }
            }
            finally {
                // Finished partial creation of this bean.
                afterSingletonCreation(beanName);
            }
 
            FactoryBean<?> fb = getFactoryBean(beanName, instance);
            if (bw != null) {
                this.factoryBeanInstanceCache.put(beanName, bw);
            }
            return fb;
        }
    }
      ...
}

 

ThreadLocal 线程单例

线程单例使用ThreadLocal来实现, ThreadLocal 不能保证其创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全。下面我们来看代码:
public class ThreadLocalSingleton {
	//ThreadLocal不能保证其创建的对象全局唯一,但可以保证在单个线程中是唯一的,天生的线程安全。
	private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>() {
		@Override
		protected ThreadLocalSingleton initialValue() {
			// TODO Auto-generated method stub
			return new ThreadLocalSingleton();
		}
	};
	
	//构造方法私有化
	private ThreadLocalSingleton() {};
	
	//全局访问点
	public static ThreadLocalSingleton getInstance() {
		return threadLocalInstance.get();
	}
}

测试代码

public static void main(String[] args) {
		
		System.out.println(ThreadLocalSingleton.getInstance());
		System.out.println(ThreadLocalSingleton.getInstance());
		System.out.println(ThreadLocalSingleton.getInstance());
		System.out.println(ThreadLocalSingleton.getInstance());
		System.out.println(ThreadLocalSingleton.getInstance());
		
		Thread t1 = new Thread(new ExectorThread()); 
		Thread t2 = new Thread(new ExectorThread()); 
		
		t1.start(); 
		t2.start(); 
		
		System.out.println("End");
	}
运行结果

 

   发现:在主线程 main 中无论调用多少次,获取到的实例都是同一个,都在两个子线程中分别获取到了不同的实例。
   结论:ThreadLocal线程单例为了达到线程安全的目的,给方法上锁,以时间换空间。ThreadLocal将所有的对象全部放在
ThreadLocalMap 中,为每个线程都提供一个对象,实际上是以空间换时间来实现线程间隔离的

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值