带着问题去学习
首先,我们带着几个问题,来学习下单例模式
1.如何实现一个线程安全的单例模式?
2.那是如何做到线程安全的呢?
3.你知道如何破坏单例吗?
接下来我们就跟着这几个问题来探索下单例模式
啥是单例模式
单例模式是指,确保一个类在任何情况下都有一个实例,并且提供一个全局访问点。
隐藏其所有的构造方法,属于创建型模式
1. 饿汉式单例
在单例类首次加载的时候就创建实例
缺点:
- 不管用不用,首次加载的时候就创建了实例,浪费内存空间。
public class HungrySingleton {
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
private HungrySingleton(){};
public static HungrySingleton getInstance(){
return HUNGRY_SINGLETON;
}
public void call(){
System.out.println("hello");
}
}
public class HungrySingletionTest {
public static void main(String[] args) {
HungrySingleton hungrySingleton = HungrySingleton.getInstance();
hungrySingleton.call();
}
}
2. 懒汉式单例模式
被外部调用的时候才创建实例
3. 非线程安全的实现单例方式
//这种方案是线程不安全的
public class LazySimpleSingleton {
private LazySimpleSingleton(){};
private static LazySimpleSingleton lazySimpleSingleton = null;
public static LazySimpleSingleton getInstance(){
if (lazySimpleSingleton == null){
lazySimpleSingleton = new LazySimpleSingleton();
}
return lazySimpleSingleton;
}
}
//写两个线程测试一下
public class ExectorThread implements Runnable{
public void run() {
LazySimpleSingleton lazy = LazySimpleSingleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+lazy);
}
}
//测试方法
public class LazySimpleSingletonTest {
public static void main(String[] args) {
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
t1.start();
t2.start();
}
}
执行测试方法,多执行几次,我们会发现,有一定几率 LazySimpleSingleton 被创建了两次,这也就是意味着上面的单例存在安全隐患
打印结果:
Connected to the target VM, address: '127.0.0.1:50008', transport: 'socket'
Thread-0:singleton.LazySimpleSingleton@26e865da
Thread-1:singleton.LazySimpleSingleton@69ab3312
Disconnected from the target VM, address: '127.0.0.1:50008', transport: 'socket'
4. 加锁实现线程安全的实现单例方式
那么如何优化代码,使懒汉模式的单例在线程环境下安全呢? 给 getInstance() 方法加上 synchronize 关键字
//加锁的单例模式
public class LazySimpleSingletonSyn {
private LazySimpleSingletonSyn(){};
private static LazySimpleSingletonSyn lazySimpleSingleton = null;
public synchronized static LazySimpleSingletonSyn getInstance(){
if (lazySimpleSingleton == null){
lazySimpleSingleton = new LazySimpleSingletonSyn();
}
return lazySimpleSingleton;
}
}
//进行debug时,我们会发现,当其中一个线程执行并且调用 getInstance() 方法时,另一个线程再调用getInstance() 方法,线程会由 RUNNING 变成了 MONITOR 出现了阻塞;直到第一个线程执行完,第二个线程才恢复 RUNNING
虽然使用 Synchronize 解决了线程安全问题,但是用 Synchronize 加锁,在线程数量较多的情况下,如果CPU分配压力上升,会导致大量线程出现阻塞,从而导致程序运行性能下降;
5. 双重检查线程安全的实现单例方式
如何既兼顾线程安全又能提升程序能能?我们在看代码
//双重检查锁的单例方式
public class LazySimpleSingletonDouble {
private LazySimpleSingletonDouble(){};
private static LazySimpleSingletonDouble lazySimpleSingleton = null;
public static LazySimpleSingletonDouble getInstance(){
if (lazySimpleSingleton == null){
synchronized(LazySimpleSingletonDouble.class){
if (lazySimpleSingleton == null){
lazySimpleSingleton = new LazySimpleSingletonDouble();
}
}
}
return lazySimpleSingleton;
}
}
//当第一个线程调用 getInstance() 方法时,第二个线程也调用 getInstance() ;当第一个线程执行到 Synchronize 时,第二个线程会变成 MONITOR 状态,此时的阻塞不是基于整个 LazySimpleSingleton 类的阻塞,而是在 getInstance() 方法内部阻塞。只要逻辑不复杂,对于调用者而言感知不到
用了双重检查锁的方式虽然对性能做了优化;但是 synchronized 关键字,总归是加锁,对程序的性能还是存在一定影响的;
6. 内部类的实现单例方式
下面我们在看一种使用内部类实现的方式,利用类的加载顺序,内部类是延时加载的,也就是说只会在第一次使用时加载,不使用就不加载。
public class LazySimpleSingletonInner {
private LazySimpleSingletonInner(){};
//final 保证这个方法不被重写/重载
public static final LazySimpleSingletonInner getInstance(){
return LazyHolder.lazy;
}
//不使用不加载
private static class LazyHolder{
private static final LazySimpleSingletonInner lazy = new LazySimpleSingletonInner();
}
}
这种实现方式即兼顾饿汉的内存浪费,也兼顾了 Synchronize 性能问题;内部类一定是要在方法调用之前初始化的,巧妙的避开了线程安全问题。
7. 反射破坏单例
上面介绍的单例模式,构造方法都加上了 private 修饰,如果我们用反射来调用构造反方,然后在调用 getInstance() 方法;就会生成两种不同的实例,下面看代码。
//用反射创建
public void test5(){
Class<LazySimpleSingleton> clazz = LazySimpleSingleton.class;
try {
Constructor<LazySimpleSingleton> c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
LazySimpleSingleton lazySimpleSingleton = c.newInstance();
LazySimpleSingleton lazySimpleSingleton2 = LazySimpleSingleton.getInstance();
System.out.println(lazySimpleSingleton);
System.out.println(lazySimpleSingleton2);
System.out.println(lazySimpleSingleton == lazySimpleSingleton2);
} catch (Exception e) {
e.printStackTrace();
}
}
输出结果,显然创建了两种不同的是实例
singleton.LazySimpleSingletonInner@6aa8ceb6
singleton.LazySimpleSingletonInner@2530c12
false
现在我们对其做一些限制,一旦多次重复创建,直接抛出异常,这样通过反射来创建对象时会抛出异常,来看代码
public class LazySimpleSingletonInner {
private LazySimpleSingletonInner(){};
//final 保证这个方法不被重写/重载
public static final LazySimpleSingletonInner getInstance(){
if (LazyHolder.lazy != null){
throw new RuntimeException("不可以重复创建多个实例");
}
return LazyHolder.lazy;
}
//不使用不加载
private static class LazyHolder{
private static final LazySimpleSingletonInner lazy = new LazySimpleSingletonInner();
}
}
输出结果
java.lang.RuntimeException: 不可以重复创建多个实例
at singleton.LazySimpleSingletonInner.getInstance(LazySimpleSingletonInner.java:9)
at singleton.LazySimpleSingletonTest.test5(LazySimpleSingletonTest.java:43)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
8. 序列化破坏单例
当我们一个单例对象创建好,有时候需要将对象序列化写入磁盘,下次使用的时候再读区对象,反序列化为内存对象。反序列化后的对象会重新分配内存,即重新创建,那么如果序列化的目标对象是单例对象,就违背了单例模式的初衷,相当于破坏了单例,下面看代码和输出结果
public class SeriableSingeton implements Serializable {
//序列化就是说把内存中的状态通过转换成字节码的形式
//从而转换成一个IO流,写入到其他地方,网络、磁盘、IO
//内存中的状态被永久保存下来
//反序列化,将已经持久化的自己码内容,转换成IO流
//通过IO流读取,进而将读取的内容转换成Java对象
//在转换过程中会重新创建对象 new
public final static SeriableSingeton INSTANCE = new SeriableSingeton();
private SeriableSingeton(){};
public static final SeriableSingeton getInstance(){
return INSTANCE;
}
}
public class SeriableSingetonTest {
public static void main(String[] args) {
SeriableSingeton s1 = null;
SeriableSingeton s2 = SeriableSingeton.getInstance();
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream("SeriableSingeton.obj");
ObjectOutputStream oos = new ObjectOutputStream(outputStream);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SeriableSingeton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SeriableSingeton) ois.readObject();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}catch (Exception e){
e.printStackTrace();
}
}
}
输出结果
singleton.SeriableSingeton@4783da3f
singleton.SeriableSingeton@279f2327
false
从运行结果看出,反序列化的对象和手动创建的对象是不一致的,实例化了两次,违背了单例设计初衷。那么如果保证序列化的情况下也能够实现单例,只需要增加 readResolve() 方法即可,下面看代码。
// 增加了 readResolve() 方法
public class SeriableSingeton implements Serializable {
//序列化就是说把内存中的状态通过转换成字节码的形式
//从而转换成一个IO流,写入到其他地方,网络、磁盘、IO
//内存中的状态被永久保存下来
//反序列化,将已经持久化的自己码内容,转换成IO流
//通过IO流读取,进而将读取的内容转换成Java对象
//在转换过程中会重新创建对象 new
public final static SeriableSingeton INSTANCE = new SeriableSingeton();
private SeriableSingeton(){};
public static final SeriableSingeton getInstance(){
return INSTANCE;
}
//重写readResolve方法,只不过是覆盖了反序列化出来的对象
//还是创建了两次,发生在JVM层面,相对比较安全
//之前反序列化出来的对象会被GC回收
public Object readResolve(){
return INSTANCE;
}
}
//测试类
public static void main(String[] args) {
SeriableSingeton s1 = null;
SeriableSingeton s2 = SeriableSingeton.getInstance();
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream("SeriableSingeton.obj");
ObjectOutputStream oos = new ObjectOutputStream(outputStream);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SeriableSingeton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SeriableSingeton) ois.readObject();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}catch (Exception e){
e.printStackTrace();
}
}
输出结果
singleton.SeriableSingeton@279f2327
singleton.SeriableSingeton@279f2327
true
为什么呢?
在JDK的源码中,进入 ObjectInputStream 类的 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();
}
}
}
在进入 readObjec0() 方法
private Object readObject0(Class<?> type, boolean unshared) throws IOException {
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
/*
* Fix for 4360508: stream is currently at the end of a field
* value block written via default serialization; since there
* is no terminating TC_ENDBLOCKDATA tag, simulate
* end-of-custom-data behavior explicitly.
*/
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
}
byte tc;
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}
depth++;
totalObjectRefs++;
try {
switch (tc) {
case TC_NULL:
return readNull();
case TC_REFERENCE:
// check the type of the existing object
return type.cast(readHandle(unshared));
case TC_CLASS:
if (type == String.class) {
throw new ClassCastException("Cannot cast a class to java.lang.String");
}
return readClass(unshared);
case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
if (type == String.class) {
throw new ClassCastException("Cannot cast a class to java.lang.String");
}
return readClassDesc(unshared);
case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared));
case TC_ARRAY:
if (type == String.class) {
throw new ClassCastException("Cannot cast an array to java.lang.String");
}
return checkResolve(readArray(unshared));
case TC_ENUM:
if (type == String.class) {
throw new ClassCastException("Cannot cast an enum to java.lang.String");
}
return checkResolve(readEnum(unshared));
case TC_OBJECT:
if (type == String.class) {
throw new ClassCastException("Cannot cast an object to java.lang.String");
}
return checkResolve(readOrdinaryObject(unshared));
case TC_EXCEPTION:
if (type == String.class) {
throw new ClassCastException("Cannot cast an exception to java.lang.String");
}
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);
case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
}
case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
}
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
看到TC_OBJECT中,调用了 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);
}
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;
}
发现 obj = desc.isInstantiable() ? desc.newInstance() : null; 这个方法
boolean isInstantiable() {
requireInitialized();
return (cons != null);
}
代码看出,判断cons是否为空,cons 是什么呢
/** serialization-appropriate constructor, or null if none */
private Constructor<?> cons;
cons 是构造方法,也就是判断一下构造方法是不为空,意味着只要有无参构造方法就会实例化
然后我们在回到 readOrdinaryObject() 方法,往下看
判断无参构造方法是否存在后,又调用了 desc.hasReadResolveMethod() 方法
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
判断 readResolveMethod 是否为空,不为空返回true,然后我们在看下 readResolveMethod 复制,通过全局查找如下
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
实际上就是通过反射,找到一个无参数的 readResolve 方法,然后在回到 readOrdinaryObject() 方法,找到这行 Object rep = desc.invokeReadResolve(obj);
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();
}
}
实际上是执行了下 readResolveMethod 方法,并返回值
所有,增加了readResolve() 方法返回实例,解决了破坏单例的问题,实际上是实例化了两次,只不过新创建的对象没有被返回而已;那么如果创建对象的动作反升频率增大,意味着内存开销也增大,如何解决呢,下面来看注册式单例
9. 注册式单例
注册式单例就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。注册式单例模式有两种:一种是枚举式单例模式,另一种是容器式单例模式。
枚举实现的方式
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 User {
private String id;
public String getId() {
return id;
}
}
//测试方法
public class EnumSingletonTest {
public static void main(String[] args) {
EnumSingleton e1 = null;
EnumSingleton e2 = EnumSingleton.getInstance();
e2.setData(new User());
FileOutputStream fos = null;
try {
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) {
e.printStackTrace();
}
}
}
输出结果
singleton.User@4fca772d
singleton.User@4fca772d
true
容器式单例实现的方式
比如IOC容器,IOC容器就属于容器式单例
我们在看文章开始时提出的几个问题
1.如何实现一个线程安全的单例模式?
可以通过恶汉模式,静态内部类
也可提通过懒汉模式,在对象实例化时候用 synchronize 关键字双重检查
再有就是通过枚举实现
2.那是如何做到线程安全的呢?
借助 synchronize 关键字实现线程安全我们就不说了,我们先看下恶汉模式是如何做到线程安全的
恶汉模式,静态内部,这两种方式都是通过定义静态变量,以保证单例对象在类初始化时候被实例化。这其实是利用了ClassLoader的线程安全加载机制。ClassLoader的loadClass方法在加载类的时候,实际上使用了 synchronized 关键字;所以除非被重写,这个方法在默认在线程的整个装载中都是线程安全的,所以使用类加载中创建的对象是线程安全的。loadClass 方法参考如下:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
我们在来看下枚举实现的单例 ,枚举底层依赖Enum类实现的,它的所有成员变量都是静态的,并且在静态代码块中实例化的,所以枚举是天然的线程安全的
3.你知道如何破坏单例吗?
可以通过反射破坏单例
序列化和反序列化破坏单例
为啥呢,文章上面已经说明了