单例模式详解 1.1单例模式概述
单例模式(Singleton Pattern)指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点,属于创建型设计模式。1.2单例模式的应用场景
单例模式可以保证JVM中只存在单一实例,应用场景主要有以下几个方面:
- 需要频繁创建一些类的对象,使用单例模式可以降低系统的内存压力,减少GC。
- 一些类创建实例的过程复杂且占用资源过多,或耗时较长,并且经常使用。
- 系统上需要单一控制逻辑的操作。
public class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}public static HungrySingleton getInstance(){
return instance;
}
}
上边是饿汉式单例的标准写法,可以看到饿汉式这种方式会在类加载的时候立刻初始化,并且创建单例对象,它是绝对的线程安全,因为在线程还没有访问的时候已经实例化结束,不可能存在访问安全的问题。
- 饿汉式的另一种写法
public class HungrySingleton {
private static final HungrySingleton instance;
static {
instance = new HungrySingleton();
}private HungrySingleton(){}public static HungrySingleton getInstance(){
return instance;
}
}
这种写法采用静态代码块的机制。
饿汉式单例优缺点分析 饿汉式单例的写法适用于单例对象较少的情况,这样写可以保证绝对的线程安全,执行效率比较高。但是缺点也很明显,饿汉式会在类加载的时候就将所有单例对象实例化,这样系统中如果有大量的饿汉式单例对象的存在,系统初始化的时候会造成大量的内存浪费,从而导致系统的内存不可控,换句话说就是不管对象用不用,对象都已存在,占用内存。
为了解决饿汉式写法带来内存浪费的问题,引出了懒汉式写法。
1.3.2懒汉式单例写法
- 特点:单例对象在被使用的时候才会进行实例化
public class LazySingleton {
private LazySingleton(){}private static LazySingleton instance;
public static LazySingleton getInstance(){
if (instance == null){
instance = new LazySingleton();
}
return instance;
}
}
上边是懒汉式单例的标准写法,从代码可以看出单例对象只有在被使用的时候才会进行实例化,但是这种方式又会引入一个新的问题。在多线程的环境下执行,存在线程安全问题,可能破坏单例。
验证一下:
public class ExcutorThread implements Runnable{
@Override
public void run() {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+instance);
}
}
public class LazySingletonTest {
public static void main(String[] args) {
Thread thread1 = new Thread(new ExcutorThread());
Thread thread2 = new Thread(new ExcutorThread());
thread1.start();
thread2.start();
System.out.println("end");
}
}
由于我们这里只有两条线程模拟多线程执行,需进行多次才能获取破坏单例情况,以下为捕捉到的单例被破坏情况:
文章图片
那么我们如何解决线程不安全呢?第一个应该想到的就是synchronized关键字:
public class LazySingleton {
private LazySingleton(){}private static LazySingleton instance;
public static synchronized LazySingleton getInstance(){
if (instance == null){
instance = new LazySingleton();
}
return instance;
}
}
这样我们就解决了线程安全问题,虽然目前解决了线程安全问题,但是当调用getInstance方法的线程很多,只有一个线程RUNNING,其他线程会出现阻塞,又引入了性能问题,我们来进一步优化。
- 双重检锁方式(Double Check)
public class LazySingleton {
private LazySingleton(){}private volatile static LazySingleton instance;
public static LazySingleton getInstance(){
// 是否会进行阻塞
if (instance == null){
synchronized (LazySingleton.class){
// 是否需要创建实例
if (instance == null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
减少锁的碰撞几率,并且将锁放置到getInstance内部,调用者感受不明显。
但是只要有锁就会对性能有一定影响,这时我们从类初始化的角度考虑,引出静态内部类的方式。
- 静态内部类写法
public class LazySingletonInnerClass {
private LazySingletonInnerClass(){}public static LazySingletonInnerClass getInstance(){
return LazyHolder.INSTANCE;
}private static class LazyHolder{
private static final LazySingletonInnerClass INSTANCE = new LazySingletonInnerClass();
}
}
只有在调用getInstance将内部类加载,实现懒加载,内部类只加载一次,线程安全。
思考:上边这种方式就真的完美了吗?我们所有的单例模式中构造方法私有化,我们一直在获取实例的方法下功夫,而JAVA提供的反射是完全可以破坏私有化构造方法的,接下来尝试用反射破坏:
public static void main(String[] args) {
try{
Class> clazz = LazySingletonInnerClass.class;
Constructor> declaredConstructor = clazz.getDeclaredConstructor(null);
// 开启强制访问
declaredConstructor.setAccessible(true);
// 获取两个实例破坏单例
Object instance1 = declaredConstructor.newInstance();
Object instance2 = declaredConstructor.newInstance();
}catch (Exception e){
e.printStackTrace();
}
}
这个时候构造方法被调用,破坏了单例,那我们彻底不让调用构造方法,调用构造方法时候进行抛异常,继续优化。
public class LazySingletonInnerClass {
private LazySingletonInnerClass(){
throw new RuntimeException("不允许调用构造方法");
}public static LazySingletonInnerClass getInstance(){
return LazyHolder.INSTANCE;
}private static class LazyHolder{
private static final LazySingletonInnerClass INSTANCE = new LazySingletonInnerClass();
}
}
但是在构造方法中抛出异常,着实不妥,但是JAVA又又天然不允许反射调用构造方法的类---枚举,引出枚举单例模式
- 枚举单例模式
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
验证一下:
public static void main(String[] args) {
try{
Class> clazz = EnumSingleton.class;
Constructor> c = clazz.getDeclaredConstructor(String.class, int.class);
c.setAccessible(true);
EnumSingleton instance =(EnumSingleton) c.newInstance("123", 12);
System.out.println(instance);
}catch (Exception e){
e.printStackTrace();
}
}
运行结果:
文章图片
真相大白,枚举不可用反射破坏构造方法,JDK的处理方式是最权威的,JDK枚举的特殊性,让代码实现更优雅。
至此,我们已经分析了各种创建单例对象时候出现的问题以及解决办法,但是创建完单例对象之后,有时候我们会使用序列化将对象写入磁盘,当下次使用时再从磁盘中反序列化转化为内存对象,这样也会破坏单例模式。
验证一下:
public static void main(String[] args) {
HungrySingleton s1 = null;
HungrySingleton s2 = HungrySingleton.getInstance();
try{
// 序列化
FileOutputStream fileOutputStream = new FileOutputStream("s2.obj");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(s2);
objectOutputStream.flush();
objectOutputStream.close();
// 反序列化
FileInputStream fileInputStream = new FileInputStream("s2.obj");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
s1 = (HungrySingleton) objectInputStream.readObject();
objectInputStream.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}catch (Exception e){
e.printStackTrace();
}
}
结果破坏了单例,见下图:
文章图片
那么如何保证在序列化的情况下保证单例呢?很简单,只需要增加readResolve方法。
public class HungrySingleton implements Serializable {
private static final HungrySingleton instance;
static {
instance = new HungrySingleton();
}private HungrySingleton(){}public static HungrySingleton getInstance(){
return instance;
}
private Object readResolve(){
return instance;
}
}
文章图片
这个原因的分析,因为在反序列话过程中创建出了个新对象,所以需要看下readObject源码中是如何实现的。
private final Object readObject(Class> type)
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
}if (! (type == Object.class || type == String.class))
throw new AssertionError("internal error");
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(type, 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();
}
}
}
private Object readObject0(Class> type, boolean unshared) throws IOException {
......
case TC_OBJECT:
if (type == String.class) {
throw new ClassCastException("Cannot cast an object to java.lang.String");
}
return checkResolve(readOrdinaryObject(unshared));
// 关键方法标记
......
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);
}
passHandle = handles.assign(unshared ? unsharedMarker : obj);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(passHandle, resolveEx);
}if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}handles.finish(passHandle);
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;
}
/**Returns true if represented class is serializable/externalizable and can be instantiated by the serialization runtime--i.e.,
if it is externalizable and defines a public no-arg constructor, or if it is non-externalizable and its first non-serializable
superclass defines an accessible no-arg constructor. Otherwise, returns false.**/
boolean isInstantiable() {
requireInitialized();
return (cons != null);
}
上边这个判断的意思是如果这个类是实现了serializable/externalizable,并且可以由序列化运行时实例化,则返回true,这个时候obj = desc.newInstance 就会创建一个新的对象,所以这个时候单例就被破坏了。
此时我们在往下边看,desc.hasReadResolveMethod()这个方法:
/**
* Returns true if represented class is serializable or externalizable and
* defines a conformant readResolve method.Otherwise, returns false.
*/
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
根据注释说的意思是如果我们加上这个readResolve()方法,判断结果就是true,会进入if块,在看if中Object rep = desc.invokeReadResolve(obj); 方法:
/**
* Invokes the readResolve method of the represented serializable class and
* returns the result.Throws UnsupportedOperationException if this class
* descriptor is not associated with a class, or if the class is
* non-serializable or does not define readResolve.
*/
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();
}
}
这个方法可以看出,利用反射去执行我们类中的readResolve()方法,这块readResolveMethod的赋值,可以再ObjectStreamClass类中:
private ObjectStreamClass(final Class> cl) {
this.cl = cl;
name = cl.getName();
isProxy = Proxy.isProxyClass(cl);
isEnum = Enum.class.isAssignableFrom(cl);
serializable = Serializable.class.isAssignableFrom(cl);
externalizable = Externalizable.class.isAssignableFrom(cl);
Class> superCl = cl.getSuperclass();
superDesc = (superCl != null) ? lookup(superCl, false) : null;
localDesc = this;
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
if (isEnum) {
suid = Long.valueOf(0);
fields = NO_FIELDS;
return null;
}
if (cl.isArray()) {
fields = NO_FIELDS;
return null;
}suid = getDeclaredSUID(cl);
try {
fields = getSerialFields(cl);
computeFieldOffsets();
} catch (InvalidClassException e) {
serializeEx = deserializeEx =
new ExceptionInfo(e.classname, e.getMessage());
fields = NO_FIELDS;
}if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = https://www.it610.com/article/(writeObjectMethod != null);
}
domains = getProtectionDomains(cons, cl);
writeReplaceMethod = getInheritableMethod(
cl,"writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
//关键方法标记
return null;
}
});
} else {
suid = Long.valueOf(0);
fields = NO_FIELDS;
}try {
fieldRefl = getReflector(fields, this);
} catch (InvalidClassException ex) {
// field mismatches impossible when matching local fields vs. self
throw new InternalError(ex);
}if (deserializeEx == null) {
if (isEnum) {
deserializeEx = new ExceptionInfo(name, "enum type");
} else if (cons == null) {
deserializeEx = new ExceptionInfo(name, "no valid constructor");
}
}
for (int i = 0;
i < fields.length;
i++) {
if (fields[i].getField() == null) {
defaultSerializeEx = new ExceptionInfo(
name, "unmatched serializable field(s) declared");
}
}
initialized = true;
}
这个方法已经约定方法名称readResolve,这时候执行类中的readResolve方法,直接返回已经创建的实例,所以反序列化后的结果变成了在单例类中已创建的实例对象。
1.4 单例模式的扩展 1.4.1 ioc容器的启蒙
- 容器式单例的写法
/**
* Created by yml
*/
public class ContainerSingleton {
private ContainerSingleton(){}private static Map ioc = new ConcurrentHashMap<>();
public static Object getBean(String className){
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);
}
}
}
这是容器式单例的写法,适用于需要大量创建单例对象的场景,便于管理,但是它是非线程安全的,其实spring中存储单例对象的容器就是一个Map。
1.4.2 ThreadLocal单例详解
- ThreadLocal单例写法
package org.example.Singleton;
/**
* Created by yml
*/
public class ThreadLocalSingleton {
private ThreadLocalSingleton(){}private static final ThreadLocal threadInstance =
new ThreadLocal(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
public static ThreadLocalSingleton getInstance(){
return threadInstance.get();
}
}
【单例模式详解】ThreadLocal的单例实现不能保证创建的单例全局唯一,但是可以保证单个线程内部是唯一的,所以是线程安全的。之前保证单例线程安全的方法是加锁,这种是以时间换空间的方式,将其他线程加锁,ThreadLocal是将对象保存到ThreadLocalMap中,以空间换时间保证线程安全。