Version: Next
单例模式
构造方法私有
饿汉式
- 立即创建当前类的实例化对象
- 将构造方法私有
class Hugry{
private Hugry(){}
private static final Hugry HUGRY = new Hugry();
public static Hugry getInstance() {
return HUGRY;
}
}
问题:不管对象是否真的被使用了,直接创建,有可能浪费资源
懒汉式
- 将构造方法私有
- 在调用
getInstance()
方法尝试获得对象实例时,才创建对象
class Lazy{
private Lazy(){}
private static Lazy LAZY;
public static Lazy getInstance(){
if (LAZY == null) {
LAZY = new Lazy();
}
return LAZY;
}
}
问题:多线程环境下,可能创建出多个对象
- 测试
class Lazy{
private Lazy(){
System.out.println(Thread.currentThread().getName() + "创建了一个对象");
}
private static Lazy LAZY;
public static Lazy getInstance(){
if (LAZY == null) {
LAZY = new Lazy();
}
return LAZY;
}
}
public class Demo {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
Lazy.getInstance();
},"Thread " + i).start();
}
}
}
Thread 5创建了一个对象
Thread 1创建了一个对象
Thread 9创建了一个对象
Thread 2创建了一个对象
Thread 6创建了一个对象
Thread 7创建了一个对象
Thread 4创建了一个对象
Thread 3创建了一个对象
Thread 8创建了一个对象
Thread 0创建了一个对象
显然已经不是单例了
DCL 懒汉式
解决方法——
Double Checked Locking
DCL懒汉式单例:
- 执行
getInstance()
方法时,先判断对象是否已被创建,没创建,就先把整个类模板锁起来- 在锁里在检测一次对象是否已被创建,此时如果对象未被创建,才创建实例化对象
class Lazy{
private Lazy(){
System.out.println(Thread.currentThread().getName() + "创建了一个对象");
}
private static Lazy LAZY;
public static Lazy getInstance(){
if (LAZY == null) {
synchronized (Lazy.class) {
if (LAZY == null) {
LAZY = new Lazy();
}
}
}
return LAZY;
}
}
public class Demo {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
Lazy.getInstance();
},"Thread " + i).start();
}
}
}
Thread 6创建了一个对象
问题:
- 在使用
new Lazy()
构造方法创建实例化对象时,可能有问题- 因为构造方法不是一个原子性操作,在极端情况下依然会出现并发问题
- 分配内存空间
- 执行构造方法,初始化对象
- 栈引用指向堆内存中的对象
- 这三个步骤如果发生指令重排,就会有问题,因此必须使用
Volatile
来禁止指令重排
class Lazy{
private Lazy(){
System.out.println(Thread.currentThread().getName() + "创建了一个对象");
}
private static volatile Lazy LAZY;
public static Lazy getInstance(){
if (LAZY == null) {
synchronized (Lazy.class) {
if (LAZY == null) {
LAZY = new Lazy();
}
}
}
return LAZY;
}
}
静态内部类实现
- 定义一个静态的内部类
- 在内部类中创建外部类的实例
- 在外部类创建
getInstance()
方法,返回内部类中的外部类实例
class Holder{
private Holder(){}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
反射创建实例破坏单例
虽然构造方法被设置为了私有,但是通过反射依然可以强行调用它来创造实例对象,从而破坏单例
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Lazy lazy1 = Lazy.getInstance();
Constructor<Lazy> constructor = Lazy.class.getDeclaredConstructor(null);
constructor.setAccessible(true); // 无视私有
Lazy lazy2 = constructor.newInstance();
System.out.println(lazy1.hashCode());
System.out.println(lazy2.hashCode());
}
1580066828
491044090
可以看到创建出了两个不同的实例化对象
解决方法:在构造方法中锁类模板,如果在执行构造方法时发现已经有对象了,直接抛出一个异常
class Lazy{
private Lazy(){
synchronized (Lazy.class) {
if (LAZY != null) {
throw new RuntimeException("不允许通过反射调用构造方法创建对象");
}
}
}
private static volatile Lazy LAZY;
public static Lazy getInstance(){
if (LAZY == null) {
synchronized (Lazy.class) {
if (LAZY == null) {
LAZY = new Lazy();
}
}
}
return LAZY;
}
}
public class Demo {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Lazy lazy1 = Lazy.getInstance();
Constructor<Lazy> constructor = Lazy.class.getDeclaredConstructor(null);
constructor.setAccessible(true); // 无视私有
Lazy lazy2 = constructor.newInstance();
System.out.println(lazy1.hashCode());
System.out.println(lazy2.hashCode());
}
}
Exception in thread "main" java.lang.reflect.InvocationTargetException
...
Caused by: java.lang.RuntimeException: 不允许通过反射调用构造方法创建对象
at singleton.Lazy.<init>(Demo.java:32)
... 5 more
多次用反射创建多个对象破坏单例
如果没用构造方法创建实例,而是直接用反射创建多个实例,还是会破坏单例
public class Demo {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<Lazy> constructor = Lazy.class.getDeclaredConstructor(null);
constructor.setAccessible(true); // 无视私有
Lazy lazy1 = constructor.newInstance();
Lazy lazy2 = constructor.newInstance();
System.out.println(lazy1.hashCode());
System.out.println(lazy2.hashCode());
}
}
1580066828
491044090
可以看到又创建出多个对象了
解决方法:定义一个名字奇奇怪怪的谁也不知道的标志位,通过标志位来标记构造方法是否被执行过
class Lazy{
private static boolean mySecretFlag = false;
private Lazy(){
synchronized (Lazy.class) {
if (!mySecretFlag) {
mySecretFlag = true;
} else {
throw new RuntimeException("不允许通过反射调用构造方法创建对象");
}
}
}
private static volatile Lazy LAZY;
public static Lazy getInstance(){
if (LAZY == null) {
synchronized (Lazy.class) {
if (LAZY == null) {
LAZY = new Lazy();
}
}
}
return LAZY;
}
}
Exception in thread "main" java.lang.reflect.InvocationTargetException
...
Caused by: java.lang.RuntimeException: 不允许通过反射调用构造方法创建对象
at singleton.Lazy.<init>(Demo.java:37)
... 5 more
问题:这个用来做标志的变量也可以通过反射获取并进行修改
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Constructor<Lazy> constructor = Lazy.class.getDeclaredConstructor(null);
constructor.setAccessible(true); // 无视私有
Field mySecretFlag = Lazy.class.getDeclaredField("mySecretFlag");
mySecretFlag.setAccessible(true);
Lazy lazy1 = constructor.newInstance();
mySecretFlag.set(lazy1, false);
Lazy lazy2 = constructor.newInstance();
System.out.println(lazy1.hashCode());
System.out.println(lazy2.hashCode());
}
644117698
1872034366
可以看到又成功破坏了单例
通过枚举保证单例
JDK 1.5
- 枚举本身也是一个类
- 枚举自带单例模式
在使用反射调用枚举类的无参构造方法时,会报错说不存在无参构造方法
- 通过jad工具查看源码,发现确实没有无参构造方法
- 有一个有参构造方法,参数类型为String, int
- 于是在反射调用构造方法时,调用这个有参构造方法
enum EnumSingle {
INSTANCE;
public static EnumSingle getInstance(){
return INSTANCE;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle enumSingle1 = EnumSingle.getInstance();
Constructor<EnumSingle> declaredConstructor =
EnumSingle.class.getDeclaredConstructor(String.class, int.class);
EnumSingle enumSingle2 = declaredConstructor.newInstance();
System.out.println(enumSingle1.hashCode());
System.out.println(enumSingle2.hashCode());
}
}
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at singleton.EnumSingle.main(Demo.java:89)
确实无法通过反射创建多个对象了