彻底玩转单例模式
大约 5 分钟JavaJUC并发编程
饿汉式单例
/**
* @Desc 饿汉式单例
* @Author HeJin
* @Date 2021/3/13 10:58
*/
public class Hungry {
private Hungry(){
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
懒汉式单例 - 单线程
/**
* @Desc 懒汉式单例
* @Author HeJin
* @Date 2021/3/13 11:02
*/
public class LazyMan {
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}
单线程下这个程序单例是可以的。多线程就会出问题,单例会被破坏。
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName() + " Ok");
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(LazyMan::getInstance).start();
}
}
}
结果:
Thread-1 Ok
Thread-3 Ok
Thread-4 Ok
Thread-2 Ok
Thread-0 Ok
Process finished with exit code 0
单例被破坏了。
DCL懒汉式
双重检测锁模式的懒汉式单例,也叫DCL懒汉式。
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName() + " Ok");
}
private static LazyMan lazyMan;
/**
* 双重检测锁模式的懒汉式单例 DCL懒汉式
*/
public static LazyMan getInstance(){
if (lazyMan == null){
synchronized (LazyMan.class){
if (lazyMan == null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(LazyMan::getInstance).start();
}
}
}
结果:
Thread-0 Ok
Process finished with exit code 0
这样比直接同步方法性能更高,如果已有对象,无须进入同步代码块。
完整的DCL懒汉式
/**
* new创建对象不是原子性操作
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*/
lazyMan = new LazyMan();
期望的顺序是1、2、3。假设A线程指令重排执行了1、3、2,那么B线程就会直接返回lazyMan。但是此时lazyMan还没有完成初始化构造。
完整的双重检测锁懒汉式单例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName() + " Ok");
}
// 禁止指令重排
private volatile static LazyMan lazyMan;
/**
* 双重检测锁模式的懒汉式单例 DCL懒汉式
*/
public static LazyMan getInstance(){
if (lazyMan == null){
synchronized (LazyMan.class){
if (lazyMan == null){
/**
* 不是原子性操作
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
* 有可能发生指令重排
* 期望123
* 132
*/
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(LazyMan::getInstance).start();
}
}
}
静态内部类单例
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
这个静态内部类单例也是不安全的,通过反射可以破坏单例。
反射破坏DCL懒汉式单例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName() + " Ok");
}
private volatile static LazyMan lazyMan;
/**
* 双重检测锁模式的懒汉式单例 DCL懒汉式
*/
public static LazyMan getInstance(){
if (lazyMan == null){
synchronized (LazyMan.class){
if (lazyMan == null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
// 无视私有private
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
结果:
main Ok
main Ok
com.hejin.single.LazyMan@14ae5a5
com.hejin.single.LazyMan@7f31245a
Process finished with exit code 0
从结果可以发现instance1 和instance2不是同一个对象。从构造方法打印的结果也可以判断,创建了两次。单例被破坏。
在构造方法里加锁判断。
private LazyMan(){
synchronized (LazyMan.class){
if (lazyMan != null){
throw new RuntimeException("不要试图使用反射破坏单例");
}
}
System.out.println(Thread.currentThread().getName() + " Ok");
}
结果:
main Ok
Exception in thread "main" 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.hejin.single.LazyMan.main(LazyMan.java:42)
Caused by: java.lang.RuntimeException: 不要试图使用反射破坏单例
at com.hejin.single.LazyMan.<init>(LazyMan.java:14)
... 5 more
Process finished with exit code 1
再次破坏。两个对象都通过反射创建。
public static void main(String[] args) throws Exception {
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
// 无视私有private
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
结果:
main Ok
main Ok
com.hejin.single.LazyMan@14ae5a5
com.hejin.single.LazyMan@7f31245a
Process finished with exit code 0
发现单例又被破坏了。
使用红绿灯
private static boolean fla = false;
private LazyMan(){
synchronized (LazyMan.class){
if (fla == false){
fla = true;
} else {
throw new RuntimeException("不要试图使用反射破坏单例");
}
}
System.out.println(Thread.currentThread().getName() + " Ok");
}
结果:
main Ok
Exception in thread "main" 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.hejin.single.LazyMan.main(LazyMan.java:47)
Caused by: java.lang.RuntimeException: 不要试图使用反射破坏单例
at com.hejin.single.LazyMan.<init>(LazyMan.java:18)
... 5 more
Process finished with exit code 1
反射破坏标志位的值
public static void main(String[] args) throws Exception {
// 假设已经知道了红绿灯标志位字段
Field fla = LazyMan.class.getDeclaredField("fla");
fla.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
// 无视私有private
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
fla.set(instance1, false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
结果:
main Ok
main Ok
com.hejin.single.LazyMan@7f31245a
com.hejin.single.LazyMan@6d6f6e28
Process finished with exit code 0
单例被破坏。
道高一尺,魔高一丈。
分析Constructor源码。newInstance。

枚举类型
枚举是JDK1.5出来的,自带单例模式。
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) {
EnumSingle instance = EnumSingle.INSTANCE;
EnumSingle instance1 = EnumSingle.INSTANCE;
System.out.println(instance);
System.out.println(instance1);
}
}
结果:
INSTANCE
INSTANCE
Process finished with exit code 0
分析源码

反射获取枚举
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
结果:
Exception in thread "main" java.lang.NoSuchMethodException: com.hejin.single.EnumSingle.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.hejin.single.Test.main(EnumSingle.java:20)
Process finished with exit code 1
错误显示没有空参的构造器。正常错误应该是:
IDEA骗了我们。
自己手动反编译class文件
// javap -p EnumSingle.class
Compiled from "EnumSingle.java"
public final class com.hejin.single.EnumSingle extends java.lang.Enum<com.hejin.single.EnumSingle> {
public static final com.hejin.single.EnumSingle INSTANCE;
private static final com.hejin.single.EnumSingle[] $VALUES;
public static com.hejin.single.EnumSingle[] values();
public static com.hejin.single.EnumSingle valueOf(java.lang.String);
// 空参构造
private com.hejin.single.EnumSingle();
public com.hejin.single.EnumSingle getInstance();
static {};
}
说明这个代码也骗了我们。
使用jad工具反编译
>jad -sjava EnumSingle.class
Parsing EnumSingle.class... Generating EnumSingle.java
当前目录下多了一个EnumSingle.java文件。打开文件:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingle.java
package com.hejin.single;
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/hejin/single/EnumSingle, name);
}
// 有参构造器
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
修改反射代码
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
结果:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.hejin.single.Test.main(EnumSingle.java:22)
Process finished with exit code 1
符合预期的错误。