Java设计模式创建型模式:单例模式
Posted hans774882968
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java设计模式创建型模式:单例模式相关的知识,希望对你有一定的参考价值。
枚举类单例(推荐)
Effective Java
书推荐使用这种单例模式。因为它足够简单,线程安全,且天然可以防止多实例化,即使是在面对反序列化和反射攻击时。
package singleton;
import java.lang.reflect.Method;
enum Singleton
instance;
Singleton()
System.out.println("正在实例化,对象地址:" + hashCode());
private String name;
void setName(String name)
this.name = name;
String getName()
return name;
public class EnumSingleton
public static void main(String[] args) throws ClassNotFoundException
Singleton.instance.setName("hans");
System.out.println(Singleton.instance.getName());
Singleton.instance.setName("acmer");
System.out.println(Singleton.instance.getName());
// 枚举类实现单例模式天然能对抗反射,反射拿不到构造方法
Method[] methods = Class.forName("singleton.Singleton").getDeclaredMethods();
for (Method method : methods)
System.out.println(method.getName());
静态内部类
静态内部类的写法是线程安全,也是延迟加载的。静态内部类和非静态内部类一样,都是在被调用时才会被加载。下面的代码验证了这一点。
package singleton;
class StaticInnerSingleton
private static class Holder
private static final StaticInnerSingleton instance = new StaticInnerSingleton();
private StaticInnerSingleton()
System.out.println("正在实例化,对象地址:" + hashCode());
static StaticInnerSingleton getInstance()
System.out.println("即将实例化");
return Holder.instance;
public class StaticInnerClassSingleton
public static void main(String[] args)
int ans = 0;
for (int i = 0; i < 10; ++i) ans += i * i;
System.out.println(ans + "实例化前");
StaticInnerSingleton u1 = StaticInnerSingleton.getInstance();
System.out.println("实例化完成");
StaticInnerSingleton u2 = StaticInnerSingleton.getInstance();
System.out.println(u1.hashCode() + " , " + u2.hashCode());
懒汉式
这种写法需要多做分析。网上最常看到的标准做法是double check+synchronized+volatile。
只有外层check行吗?
不行。线程1释放锁后,线程2拿到锁,把instance覆盖掉了。这肯定很荒谬啊!
只有内层check行吗?
可以,但这样的效果相当于synchronized修饰方法,每个线程都要进来阻塞一遍,效率低。
如何解决线程安全问题?
- instance要加volatile,防止指令重排序(先把指针指向对应堆空间,再初始化对象),导致另一个线程拿到空对象。值得注意的是,比较旧版的JDK,volatile不能满足“有序性”,于是下面的代码在旧版的JDK不是线程安全的。
- 需要一个对象锁来防止被多个线程实例化。
为什么需要volatile?
对象创建过程:1.分配对象内存空间。 2.初始化对象。 3.设置对象指向内存空间。
2和3是没有依赖关系的,所以JVM指令重排序可能会把指令排成132。如果线程1完成了13,线程2在外层check(没有进入阻塞等锁的阶段)时就判定instance != null
,于是拿到了没有初始化的对象。
值得注意的是,如果直接锁静态方法,那么即使可能有指令重排,也不需要volatile,因为其他线程进不去外层check。但这样效率就低了。
如何处理反射攻击?
参考链接说“在构造函数加一个判定instance是否null”的判定即可,其实不完全对。如果第一次生成是用的静态方法,那么没问题;如果所有实例都用反射的方式生成,那么以上简单的做法就没法防范。
我自己乱搞了一个4次check的做法,是为了解决反射攻击的问题,运行看上去没啥问题,要是这种乱搞做法有问题可以评论区告诉我QAQ。
package singleton;
import java.lang.reflect.Constructor;
public class LazySingleton
private static volatile LazySingleton instance = null;
private String name;
private LazySingleton(String name)
if (instance != null) throw new IllegalStateException("禁止多次初始化!");
synchronized (LazySingleton.class)
if (instance != null) throw new IllegalStateException("禁止多次初始化!");
System.out.println("正在实例化,对象地址:" + hashCode());
this.name = name;
instance = this;
// 1-instance要加volatile,防止指令重排序(先把指针指向对应堆空间,再初始化对象),
// 导致另一个线程拿到空对象。
// 2-需要一个对象锁来防止被多个线程实例化。
public static LazySingleton getInstance(String name)
if (instance != null) return instance;
synchronized (LazySingleton.class)
if (instance != null) return instance;
instance = new LazySingleton(name);
return instance;
void setName(String name)
this.name = name;
String getName()
return name;
public static void main(String[] args) throws Exception
Thread t1 = new Thread(() ->
LazySingleton user = LazySingleton.getInstance("hans");
System.out.println(user.getName() + user.hashCode());
), t2 = new Thread(() ->
LazySingleton user = LazySingleton.getInstance("xyz");
System.out.println(user.getName() + user.hashCode());
);
t1.start();
t2.start();
// Class<LazySingleton> clazz = (Class<LazySingleton>) Class.forName("singleton.LazySingleton");
// Constructor<LazySingleton> cons = clazz.getDeclaredConstructor(String.class);
// LazySingleton u1 = cons.newInstance("hans"), u2 = cons.newInstance("hans");
// System.out.println("hashCode:u1 = " + u1.hashCode() + ", u2 = " + u2.hashCode());
饿汉式
这种写法很简单,我就直接拿别人的例子了。
因为对象创建时机足够早,所以自然不存在线程安全问题。
缺点:即使单例没有被使用,对象也会被创建,占用资源。
package com.junmoyu.singleton;
/**
* 饿汉式单例模式 - 线程安全
* 该类在程序加载时就已经初始化完成了
*/
public class EagerlySingleton
/**
* 初始化静态实例
*/
private static EagerlySingleton INSTANCE = new EagerlySingleton();
/**
* 私有构造函数,保证无法从外部进行实例化
*/
private EagerlySingleton()
System.out.println(getClass().getCanonicalName() + " 被实例化,hashCode:" + hashCode());
/**
* 可被用户调用以获取类的实例
*/
public static EagerlySingleton getInstance()
return INSTANCE;
public static void main(String[] args) throws Exception
// 延迟加载测试
System.out.println("测试代码启动");
Thread.sleep(1000);
// 多线程测试
for (int i = 0; i < 5; i++)
new Thread(() -> System.out.println("多线程测试:hashCode:" + "@" + EagerlySingleton.getInstance().hashCode())).start();
参考
https://juejin.cn/post/6958740833482997790
静态内部类何时初始化:https://www.cnblogs.com/maohuidong/p/7843807.html
以上是关于Java设计模式创建型模式:单例模式的主要内容,如果未能解决你的问题,请参考以下文章