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

将该文件复制到刚才解压的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.()

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

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

filter java 是单例的吗_JAVA 设计模式之 单例模式详解相关推荐

  1. java connection 单例_Java设计模式之单例模式详解

    Java设计模式之单例模式详解 什么是设计模式 设计模式是在大量的实践中总结和理论之后优选的代码结构,编程风格,以及解决问题的思考方式.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可 ...

  2. java单例设计模式_Java设计模式之单例模式详解

    在Java开发过程中,很多场景下都会碰到或要用到单例模式,在设计模式里也是经常作为指导学习的热门模式之一,相信每位开发同事都用到过.我们总是沿着前辈的足迹去做设定好的思路,往往没去探究为何这么做,所以 ...

  3. java构造单例线程池_java中常见的六种线程池详解

    之前我们介绍了线程池的四种拒绝策略,了解了线程池参数的含义,那么今天我们来聊聊Java 中常见的几种线程池,以及在jdk7 加入的 ForkJoin 新型线程池 首先我们列出Java 中的六种线程池如 ...

  4. java 内部类 单例_确保对象的唯一性——单例模式 (四):一种更好的单例实现方法(静态内部类)...

    3.5 一种更好的单例实现方法 饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存:懒汉式单例类线程安全控制烦琐,而且性能受影响.可见,无论是饿汉式单例还是懒汉式单例都存在这样那样的问题,有没有 ...

  5. socket可以写成单例嘛_精读设计模式 Singleton 单例模式

    Singleton(单例模式) Singleton(单例模式)属于创建型模式,提供一种对象获取方式,保证在一定范围内是唯一的. 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 其实单例模 ...

  6. java所使用的字符集是_Java常用字符集编码详解

    Java常用字符集编码详解 Web开发的时候经常会遇到一些字符编码的错误,如页面乱码等问题,所以有必要需对字符编码有所了解,以下是Ricki收集的一些资料(可能不是很全,但希望对你有所帮助) Java ...

  7. java中实现具有传递性吗_Java中volatile关键字详解,jvm内存模型,原子性、可见性、有序性...

    一.Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的. Java内存模型规定了所有的变量都存储在主内存中.每条线程中还有自己的工作内存,线程的工作 ...

  8. 设计模式java装饰模式范例_Java设计模式之装饰模式详解

    装饰者模式是动态地将责任附加到对象上.若要扩展功能,装饰者提供了比继承更有弹性的替代方案. 假设我们有一个需求,是给一家饮料店做一个计算各种饮料价格的功能.听起来很简单,我们创建一个抽象父类Bever ...

  9. java基本数据类型所占长度_java基本数据类型长度详解

    java基本数据类型 基础知识 机器数和真值 机器数 一个数在计算机中的二进制表示形式,叫做这个数的机器数 机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1 例如: dec ...

最新文章

  1. 摄像头Camera标定Calibration原理Theory
  2. HDU 2147 kiki's game
  3. java jar包收集
  4. 然而毕博淮安的毕博淮安
  5. 广度优先搜索解决欧拉回路时间复杂度_迷宫搜索类的双向bfs问题(例题详解)
  6. 思科命令 service password-encryption
  7. [转载] Python Pandas 的 all和any方法
  8. 【矩阵论】线性空间与线性变换(3)(4)
  9. 【linux内核分析与应用-陈莉君】系统调用机制
  10. Android 10.0 Launcher3 抽屉式(双层)app列表排序
  11. 软件工程实践者的思想
  12. Day1—图片动态过渡
  13. StackOverflow和OutOfMemory
  14. X710网卡RSS对称哈希
  15. 阿里云 + Ubuntu + WordPress 建造个人博客网站
  16. jtopo 拓扑图的简单使用
  17. [Spring实战系列](13)使用注解自动装配
  18. 删除数组中重复出现的值
  19. Pytorch实现102类鲜花分类(102 Category Flower Dataset)
  20. 如何放大淘客的收入。

热门文章

  1. 轻量级分布式文件系统FastDFS使用安装说明手册(新手入门级)
  2. SRM遇到的一个数论技巧——最大公约数和最小公倍数的关系
  3. WPF下可编辑Header的Tab控件实现
  4. 史上最昂贵的 Javascript 代码(转)
  5. postgres创建用户,修改用户密码,创建数据库
  6. python 错误 Could not find a suitable TLS CA certificate bundle, invalid path 解决方法
  7. 使用SHA1、SHA2双证书进行微软数字签名
  8. golang beego框架对运行异常的处理
  9. docker错误:Error response from daemon: Cannot start container
  10. Linux2.6内核--对块IO层操作的讨论