filter java 是单例的吗_JAVA 设计模式之 单例模式详解

单例模式:(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.(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文件

2003ef314a9315c3a5878de754bd4ccf.png

将该文件复制到刚才解压的jad.exe 所在目录,当前目录打开cmd命令窗口,输入 jad EnumSingleton.class ,该目录下就会生成一个EnumSingleton.jad文件

3a0be16edf27251c3051b81cec67248c.png

打开EnumSingleton.jad文件,你会惊奇地发现里面有一个static方法

88eda02ee96616ae246b7a7014ebe960.png

因此我们得知,枚举式单例在静态代码块中就给 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.()

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 枚举的语法特殊性,加上反射也为枚举类保驾护航,让枚举式单例更受程序员的追捧。

2、容器式

**

* @Description 容器式单例模式

* @Author Bert

* @Date 2019\5\29 0029

*/

public class ContainerSingleton {

//私有的构造方法

private ContainerSingleton(){}

//存储实例的map,ConcurrentHashMap中线程安全,spring框架的IOC注册中心就是用这种方式实现的

private static Map ioc = new ConcurrentHashMap();

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 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 threadLocal = new ThreadLocal(){

@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

同一个线程里面都是唯一的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值