单例模式:(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。单例模式在现实生活中应用也非常广泛。 在 J2EE 标准中,ServletContext、 ServletContextConfig 等;在 Spring 框架应用中 ApplicationContext;数据库的连接 池也都是单例形式。
一、饿汉模式
饿汉模式,是在类加载的时候立即初始化,并实例对象,线程绝对按全,在线程还没出现前就已经实例化,不存在访问安全问题
优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存。
Spring 中 IOC 容器 ApplicationContext 本身就是典型的饿汉式单例。
下面我们来看饿汉模式的两种基本写法
/**
* 第一种写法
* @Description 单例模式:全局只能有一个实例,构造方法私有化,对外提供一个全局访问点
* 这是一个简单的饿汉式 单例模式,类加载的时候,就会初始化,并创建已经静态常量实例。
* 绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题
* 优点:没有加任何的锁、执行效率比较高,
* 缺点:如果有很多个单例,都是饿汉模式,类加载的时候就初始化很影响性能,不管用不用,都占着空间,浪费了内存
* @Author Bert
* @Date 2019\5\25 0025
*/
public class HungerSingleton {
private static final HungerSingleton SINGLETON = new HungerSingleton();
//构造方法私有化
private HungerSingleton() {}
//对外提供一个全局访问点
public static HungerSingleton getInstance(){
return SINGLETON;
}
}
/**
* 第二种写法
* @Description 饿汉模式 静态类加载块写法
* 在静态代码块里面创建一个静态常量实例
* @Author Bert
* @Date 2019\5\25 0025
*/
public class HungerStaticSingleton {
private static final HungerStaticSingleton SINGLETON;
//静态代码块,类加载的时候初始化
static {
SINGLETON = new HungerStaticSingleton();
}
//私有的构造方法
private HungerStaticSingleton(){}
//全局访问点
public static HungerStaticSingleton getInstance(){
return SINGLETON ;
}
}
/**
* @Description 单例模式 多线程测试类
* @Author Bert
* @Date 2019\5\25
*/
public class HungerSingletonThread extends Thread{
@Override
public void run() {
HungerSingleton singleton = HungerSingleton.getInstance();
System.out.println("HungerSingleton:"+Thread.currentThread().getName()+":"+singleton);
HungerStaticSingleton singleton1 = HungerStaticSingleton.getInstance();
System.out.println("HungerStaticSingleton"+Thread.currentThread().getName()+":"+singleton1);
}
}
/**
* @Description 饿汉模式测试类
* @Date 2019\5\25
*/
public class HungerSingletonTest {
public static void main(String[] args) {
//饿汉 单例模式 测试
new Thread(new HungerSingletonThread()).start();
new Thread(new HungerSingletonThread()).start();
new Thread(new HungerSingletonThread()).start();
new Thread(new HungerSingletonThread()).start();
new Thread(new HungerSingletonThread()).start();
new Thread(new HungerSingletonThread()).start();
}
}
运行结果如下
HungerSingleton:Thread-3:com.bert.singleton.hunger.HungerSingleton@54477291
HungerStaticSingletonThread-3:com.bert.singleton.hunger.HungerStaticSingleton@5a808669
HungerSingleton:Thread-1:com.bert.singleton.hunger.HungerSingleton@54477291
HungerStaticSingletonThread-1:com.bert.singleton.hunger.HungerStaticSingleton@5a808669
HungerSingleton:Thread-9:com.bert.singleton.hunger.HungerSingleton@54477291
HungerStaticSingletonThread-9:com.bert.singleton.hunger.HungerStaticSingleton@5a808669
HungerSingleton:Thread-7:com.bert.singleton.hunger.HungerSingleton@54477291
HungerStaticSingletonThread-7:com.bert.singleton.hunger.HungerStaticSingleton@5a808669
HungerSingleton:Thread-5:com.bert.singleton.hunger.HungerSingleton@54477291
HungerStaticSingletonThread-5:com.bert.singleton.hunger.HungerStaticSingleton@5a808669
HungerSingleton:Thread-11:com.bert.singleton.hunger.HungerSingleton@54477291
HungerStaticSingletonThread-11:com.bert.singleton.hunger.HungerStaticSingleton@5a808669
这两种写法都非常的简单,也非常好理解,饿汉式适用在单例对象较少的情况。
二、懒汉模式
懒汉模式:被外部类调用的时候内部类才会加载,线程不安全。
1、简单的懒汉模式:
/**
* @Description 简单的懒汉式 单例 私有化的构造方法 对外提供一个全局访问点, 调用的时候初始化
* 线程不安全,可以用idea 在线程模式下deg,当两个以上线程同时进入就会各自创建实例
* @Author Bert
* @Date 2019\5\25 0025
*/
public class LazySimpleSingleton {
private static LazySimpleSingleton singleton = null;
//私有化的构造方法
private LazySimpleSingleton(){}
//对外提供一个全局访问点
public static LazySimpleSingleton getInstance(){
if(null == singleton)
singleton = new LazySimpleSingleton();
return singleton;
}
}
/**
* @Description 懒汉 单例模式 多线程测试类
* @Author Bert
* @Date 2019\5\25 0025
*/
public class LazySingletonThread extends Thread{
@Override
public void run() {
LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+singleton);
}
}
/**
* @Description 懒汉模式测试类
* @Author Bert
* @Date 2019\5\25 0025
*/
public class LazyleSingletonTest {
public static void main(String[] args) {
new Thread(new LazySingletonThread()).start();
new Thread(new LazySingletonThread()).start();
}
}
用idea在多线程情况下deg让两个线程同时进入到getInsstance()方法中就活出现以下结果:
Thread-3:com.bert.singleton.lazy.LazySimpleSingleton@d9362c6
Thread-1:com.bert.singleton.lazy.LazySimpleSingleton@7a9f5b3f
这就足以证明线程不安全,下面我们再看另一种优化写法,在getInstance()方法上面加synchronized关键字
/**
* @Description 简单的懒汉式 单例 私有化的构造方法 对外提供一个全局访问点, 调用的时候初始化
* 线程不安全,可以用idea 在线程模式下deg,当两个以上线程同时进入就会各自创建实例
* @Author Bert
* @Date 2019\5\25 0025
*/
public class LazySimpleSingleton {
private static LazySimpleSingleton singleton = null;
//私有化的构造方法
private LazySimpleSingleton(){}
//对外提供一个全局访问点, 在该方法上面加上synchronized 关键字,使其变成线程同步方法
public static synchronized LazySimpleSingleton getInstance(){
if(null == singleton)
singleton = new LazySimpleSingleton();
return singleton;
}
}
再看运行结果:
Thread-1:com.bert.singleton.lazy.LazySimpleSingleton@3d437d89
Thread-3:com.bert.singleton.lazy.LazySimpleSingleton@3d437d89
但是当我们将其中一个线程执行并调用 getInstance()方法时,另一 个线程在调用 getInstance()方法,线程的状态由 RUNNING 变成了 MONITOR,出现阻塞。直到第一个线程执行完,第二个线程才恢复 RUNNING 状态继续调用 getInstance(),完美的展现了 synchronized 监视锁的运行状态,线程安全的问题便解决了。但是,用 synchronized 加锁,在线程数量比较多情况下,如果 CPU 分配压力上升,会导致大批 量线程出现阻塞,从而导致程序运行性能大幅下降。接下来就看下一种,既兼顾线程安全又提升程序性能的懒汉模式
2.双重检查锁的单例模式
/**
* @Description 简单的懒汉式 单例 私有化的构造方法 对外提供一个全局访问点, 调用的时候初始化
* 线程不安全,可以用idea 在线程模式下deg,当两个以上线程同时进入就会各自创建实例
* @Author Bert
* @Date 2019\5\25 0025
*/
public class LazySimpleSingleton {
private static LazySimpleSingleton singleton = null;
//私有化的构造方法
private LazySimpleSingleton(){}
//对外提供一个全局访问点, 既保证性能,也保证线程安全的双重检查锁
public static LazySimpleSingleton getInstance(){
if(null == singleton)
synchronized(LazySimpleSingleton.class){
if(null == singleton)
singleton = new LazySimpleSingleton();
}
return singleton;
}
}
这种线程也是安全的,但是,用到 synchronized 关键字,总归是要上锁,对程序性能还是存在一定影响的。下面请看静态内部类方式
3、静态内部类的懒汉模式
/**
* @Description 静态内部类 兼容饿汉模式的 懒汉模式
* 默认使用 LazyStaticSingleton 的时候,会先初始化内部类
* 如果没使用的话,内部类是不加载的
* @Author Bert
* @Date 2019\5\25 0025
*/
public class LazyStaticSingleton {
//私有构造
private LazyStaticSingleton(){}
//对外访问点,static 为了是单例的空间共享, final 保证这个方法不会被重写,重载
public static final LazyStaticSingleton getInstance(){
//在返回结果以前,一定会先加载内部类
return lazySingleton.LAZY;
}
//静态内部类 ,默认不加载
private static class lazySingleton{
private static final LazyStaticSingleton LAZY = new LazyStaticSingleton();
}
}
这种形式兼顾饿汉式的内存浪费,也兼顾 synchronized 性能问题。内部类一定是要在方 法调用之前初始化,巧妙地避免了线程安全问题。
三、防止反射破坏单例
前面单例模式的构造方法除了加上 private 以外,没有做任何处 理。如果我们使用反射来调用其构造方法,然后,再调用 getInstance()方法,应该就会 两个不同的实例。
下面我们通过反射调用单例模式,以LazyStaticSingleton为例
/**
* @Description 通过反射机制强行调用单例
* @Author Bert
* @Date 2019\5\25 0025
*/
public class LazyleSingletonTest {
public static void main(String[] args) {
try {
//
Class<?> clazz = LazyStaticSingleton.class;
//通过反射 获取私有的构造方法
Constructor c = clazz.getDeclaredConstructor(null);
//强制访问
c.setAccessible(true);
//暴力初始化
Object o1 = c.newInstance();
//调用两次构造方法,相当于new 两次,犯了原则性错误。
Object o2 = c.newInstance();
System.out.println(o1);
System.out.println(o2);
}catch (Exception e){
e.printStackTrace();
}
}
}
运行结果如下:
com.bert.singleton.lazy.LazyStaticSingleton@4b67cf4d
com.bert.singleton.lazy.LazyStaticSingleton@7ea987ac
由此可见,我们应该再多进一步处理,在构造方法中做一些限制,一旦出现多次创建,就抛出一个异常
/**
* @Description 静态内部类 兼容饿汉模式的 懒汉模式
* 默认使用 LazyStaticSingleton 的时候,会先初始化内部类
* 如果没使用的话,内部类是不加载的
* @Author Bert
* @Date 2019\5\25 0025
*/
public class LazyStaticSingleton {
//私有构造
private LazyStaticSingleton(){
if(lazySingleton.LAZY != null)
throw new RuntimeException("warning:Unauthorized access!");
}
//对外访问点,static 为了是单例的空间共享, final 保证这个方法不会被重写,重载
public static final LazyStaticSingleton getInstance(){
//在返回结果以前,一定会先加载内部类
return lazySingleton.LAZY;
}
//静态内部类 ,默认不加载
private static class lazySingleton{
private static final LazyStaticSingleton LAZY = new LazyStaticSingleton();
}
}
运行结果如下:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.bert.singleton.LazyleSingletonTest.main(LazyleSingletonTest.java:24)
Caused by: java.lang.RuntimeException: warning:Unauthorized access!
at com.bert.singleton.lazy.LazyStaticSingleton.<init>(LazyStaticSingleton.java:15)
... 5 more
在构造方法里面加判断后,再通过反射强制创建就出抛出我们刚设定的异常。
四、防止序列化破坏单例
当我们将一个单例对象创建好,有时候需要将对象序列化然后写入到磁盘,下次使用时 再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存, 即重新创建。那如果序列化的目标的对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例。
我们以 一个简单的 饿汉模式为例
/**
* @Description 序列 单例模式
* @Author Bert
* @Date 2019\5\25 0025
*/
public class SeriableSingleton implements Serializable {
private final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){
if (INSTANCE != null)
throw new RuntimeException("警告:非法操作!");
}
public static SeriableSingleton getInstance() {
return INSTANCE;
}
}
/**
* @Description 反序列 单例模式 测试
* @Date 2019\5\25 0025
*/
public class SeriableSingletonTest {
public static void main(String[] args) {
SeriableSingleton s1 = null;//通过反序列化获取
SeriableSingleton s2 = SeriableSingleton.getInstance();
FileOutputStream fos = null;
try {
//序列化
//序列化就是说把内存中的状态通过转换成字节码的形式
//从而转换一个 IO 流,写入到其他地方(可以是磁盘、网络 IO)
//内存中状态给永久保存下来了
fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
//反序列化
//讲已经持久化的字节码内容,转换为 IO 流
//通过 IO 流的读取,进而将读取的内容转换为 Java 对象
//在转换过程中会重新创建对象 new
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Object o = ois.readObject();
System.out.println(s2);
System.out.println(o);
}catch (Exception e){
e.printStackTrace();
}
}
}
运行结果:
com.bert.singleton.seriable.SeriableSingleton@19dfb72a
com.bert.singleton.seriable.SeriableSingleton@2a5ca609
运行结果中,可以看出,反序列化后的对象和手动创建的对象是不一致的,实例化了两次,违背了单例的设计初衷。
优化解决方案,添加readResolve()方法
/**
* @Description 序列 单例模式
* @Author Bert
* @Date 2019\5\25 0025
*/
public class SeriableSingleton implements Serializable {
private final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){
if (INSTANCE != null)
throw new RuntimeException("警告:非法操作!");
}
public static SeriableSingleton getInstance() {
return INSTANCE;
}
//防止反序列化 破坏单例,
private Object readResolve(){
return INSTANCE;
}
}
运行结果:
com.bert.singleton.seriable.SeriableSingleton@6e0be858
com.bert.singleton.seriable.SeriableSingleton@6e0be858
这样已经解决这个问题了,为什么要这么写呢,其实在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、枚举式
/**
* @Description 枚举式 (注册式单例)
* @Author Bert
* @Date 2019\5\25 0025
*/
public enum EnumSingleton {
INSTANCE;
//赋值的数据
private Object object;
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
/**
* @Description 枚举式单例 反序列化 测试
* @Date 2019\5\25 0025
*/
public class EnumSingletonTest {
public static void main(String[] args) {
EnumSingleton instance1 = null;
EnumSingleton instance2 = EnumSingleton.getInstance();
instance2.setObject(new Object());
try {
//序列化
FileOutputStream fos = new FileOutputStream("EnumSingletonTest.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance2);
oos.flush();
oos.close();
//反序列化
FileInputStream fis = new FileInputStream("EnumSingletonTest.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
instance1 = (EnumSingleton)ois.readObject();
ois.close();
System.out.println(instance1.getObject());
System.out.println(instance2.getObject());
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果如下:
java.lang.Object@3feba861
java.lang.Object@3feba861
接下来我就开始解密为什么枚举式的单例可以如此神奇,这需要从源码入手,先下载一个java反编译工具jad(下载地址:https://varaneckas.com/jad/),下载后解压就可以使用命令行调用了,先找到项目中编译后的EnumSingleton.class文件
将该文件复制到刚才解压的jad.exe 所在目录,当前目录打开cmd命令窗口,输入 jad EnumSingleton.class ,该目录下就会生成一个EnumSingleton.jad文件
打开EnumSingleton.jad文件,你会惊奇地发现里面有一个static方法
因此我们得知,枚举式单例在静态代码块中就给 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;
}
从readEnum()方法中,我们得知枚举类是通过类名和class对象找到一个唯一的枚举对象,因此,枚举对 象不可能被类加载器加载多次。
接下来,我们在测试一下枚举类单例会不会被发射破坏。
public class EnumSingletonTest {
public static void main(String[] args) {
newInstanceTest();
}
//反射测试
public static void newInstanceTest() {
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: com.bert.singleton.register.EnumSingleton.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.bert.singleton.register.EnumSingletonTest.newInstanceTest(EnumSingletonTest.java:22)
at com.bert.singleton.register.EnumSingletonTest.main(EnumSingletonTest.java:15)
报的是java.lang.NoSuchMethodException 异常,意思是没找到无参的构造方法。我们打开java.lang.Enum的源码,发现只有一个protected的构造方法
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
那我们就传入两个参数,再试一次
public class EnumSingletonTest {
public static void main(String[] args) {
newInstanceTest();
}
//反射测试
public static void newInstanceTest() {
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();
}
}
}
运行结果:
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.bert.singleton.register.EnumSingletonTest.newInstanceTest(EnumSingletonTest.java:24)
at com.bert.singleton.register.EnumSingletonTest.main(EnumSingletonTest.java:15)
报错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()方法中我们可以看出,代码中做出来强制判断,如果修饰词是ENUM,直接抛出异常,因此,我们可以肯定枚举式单例模式不可能被反射破坏。由于JDK 枚举的语法特殊性,加上反射也为枚举类保驾护航,让枚举式单例更受程序员的追捧。
、容器式
**
* @Description 容器式单例模式
* @Author Bert
* @Date 2019\5\29 0029
*/
public class ContainerSingleton {
//私有的构造方法
private ContainerSingleton(){}
//存储实例的map,ConcurrentHashMap中线程安全,spring框架的IOC注册中心就是用这种方式实现的
private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
public static Object getBean(String className){
synchronized (ioc){
//如果map中没有这个class实例
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);
}
}
}
虽然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 不能保证其 创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全。
/**
* @Description 线程单例
* @Author Bert
* @Date 2019\5\30 0030
*/
public class ThreadLocalSigleton {
//ThreadLocal不能保证其创建的对象全局唯一,但可以保证在单个线程中是唯一的,天生的线程安全。
private static final ThreadLocal<ThreadLocalSigleton> threadLocal = new ThreadLocal<ThreadLocalSigleton>(){
@Override
protected ThreadLocalSigleton initialValue() {
return new ThreadLocalSigleton();
}
};
private ThreadLocalSigleton(){};
public static ThreadLocalSigleton getInstance(){
return threadLocal.get();
}
}
/**
* @Description 线程单例测试类
* @Author Bert
* @Date 2019\5\30
*/
public class SingletonThread extends Thread{
@Override
public void run() {
ThreadLocalSigleton singleton = ThreadLocalSigleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+singleton);
ThreadLocalSigleton singleton1 = ThreadLocalSigleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+singleton1);
ThreadLocalSigleton singleton2 = ThreadLocalSigleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+singleton2);
ThreadLocalSigleton singleton3 = ThreadLocalSigleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+singleton3);
}
}
public class ThreadLocalSigletonTest {
//测试线程单例
public static void main(String[] args) {
new Thread(new SingletonThread()).start();
new Thread(new SingletonThread()).start();
new Thread(new SingletonThread()).start();
}
}
运行结果:
Thread-1:com.bert.singleton.thread.ThreadLocalSigleton@4695cf5f
Thread-1:com.bert.singleton.thread.ThreadLocalSigleton@4695cf5f
Thread-1:com.bert.singleton.thread.ThreadLocalSigleton@4695cf5f
Thread-1:com.bert.singleton.thread.ThreadLocalSigleton@4695cf5f
Thread-3:com.bert.singleton.thread.ThreadLocalSigleton@51dca821
Thread-3:com.bert.singleton.thread.ThreadLocalSigleton@51dca821
Thread-3:com.bert.singleton.thread.ThreadLocalSigleton@51dca821
Thread-3:com.bert.singleton.thread.ThreadLocalSigleton@51dca821
Thread-5:com.bert.singleton.thread.ThreadLocalSigleton@25c8063f
Thread-5:com.bert.singleton.thread.ThreadLocalSigleton@25c8063f
Thread-5:com.bert.singleton.thread.ThreadLocalSigleton@25c8063f
Thread-5:com.bert.singleton.thread.ThreadLocalSigleton@25c8063f
同一个线程里面都是唯一的