JUC详解 -> 单例模式 - 四种实现方式&反射破坏及解决办法
Volatile --> 单例模式!
饿汉式 DCL懒汉式
枚举为什么可以避免单例模式被破坏?
1. 什么是单例模式
- 当需要某个类只能被实例化一次的时候,就要使用单例模式。
- 单例模式是比较简单的一种设计模式。
- 一个类如何才能只被实例化一次? 一般实例化对象时,都会调用类的构造方法,因此将该类的构造方法private,就不能向外界提供,即保证了该类对象的唯一性。但这样的话,就无法调用这个类的构造方法了,也就无法实例化对象了,那么由谁来实例化对象呢?只能由类自身调用了。
代码测试
public class Singleton {
//私有化构造方法
private Singleton(){}
//在类中定义getInstance()方法来提供一个该类的实例化对象
//将这个方法表示为静态方法static就可以调用这个方法了
public static Singleton getInstance(){
return new Singleton();
}
}
//这样就可以直接调用Singleton.getInstance()来创建对象
2. 饿汉式单例模式
//饿汉式单例模式
public class Hungry {
//构造器私有
private Hungry(){}
//问题:假如代码中都还没用到这个类的实例化对象,如果这个类很复杂,可能会浪费空间
//解决:最好是当要使用这个实例对象时再创建 --懒汉式
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
//在类中事先创建好一个实例对象,每次调用getInstance()返回这个对象即可
//只要这个类一加载完,就会创建实例对象。——饿汉式单例模式
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
3. 懒汉式单例模式
- 懒汉式单例模式:当使用这个实例对象时再创建;
- 解决了上述饿汉式单例模式带来的资源浪费的问题。
- 但是会存在问题:如创建10个线程,就不安全了,有时候会踹下你类被实例化了多次。
//懒汉式单例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+" ok");
}
//只有当用到的时候才创建对象
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan == null){ //------------1
lazyMan = new LazyMan(); //------------2
}
return lazyMan;
}
//问题:单线程下没有问题;
//多线程并发情况下会有问题 : 不安全
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
lazyMan.getInstance();
}).start();
}
}
}
-
为什么会出现这种问题?
- 假如两个线程A和B;
- 首先A调用了getInstance(),当执行到 if(LazyMan == null) 时,会判断对象是否为空,条件成立,就进入花括号中执行语句 LazyMan = new LazyMan();,若此时A突然停了,CPU切换执行线程B;
- 当B执行getInstance()时,也会和A一样,先判断if(LazyMan == null)是否成立,但此时A线程停止了还没有创建实例,所以该条件还是成立,B也会进入花括号执行LazyMan = new LazyMan();,此时B顺利执行完,创建了一个实例并返回了;
- 然后CPU又切换去执行A,此时A继续执行下面的语句 LazyMan = new LazyMan();,也创建了一个实例,并返回。
- 因此,破坏了单例模式。
-
那如何解决该问题?
-
①在getInstance()前加关键字synchronized,即synchronized同步方法
public class LazyMan { private LazyMan(){ System.out.println(Thread.currentThread().getName()+" ok"); } //只有当用到的时候才创建对象 private static LazyMan lazyMan; //synchronized关键字修饰getInstance(),即同步方法 public static synchronized LazyMan getInstance(){ if(lazyMan == null){ lazyMan = new LazyMan(); } return lazyMan; } } //synchronized解决了该问题,但是synchronized会降低程序执行效率。
-
②在LazyMan的类对象LazyMan.class前加关键字synchronized,synchronized同步代码块
//DCL懒汉式单例模式 public class LazyMan { private LazyMan(){ System.out.println(Thread.currentThread().getName()+" ok"); } //只有当用到的时候才创建对象 private static LazyMan lazyMan; //实例的重复创建问题本质还是在实例还没有被创建时,因此,只要实例创建成功了就没有必要加锁了。因此只要给实例还没创建时的if(lazyMan == null){lazyMan = new LazyMan();}语句块加锁即可,即同步代码块 //双重检验锁模式(Double Checked Locking Pattern) public static LazyMan getInstance(){ if(lazyMan == null){ synchronized(LazyMan.class){ if(lazyMan == null){ lazyMan = new LazyMan(); } } } return lazyMan; } } //存在的问题:在极端情况下,由于lazyMan = new LazyMan()不是原子性操作,会出现指令重排的问题
DCL懒汉式单例模式解决了多线程并发下重复创建实例的问题,同时也提高了代码的执行效率。
但还存在问题!指令重排的问题!在极端情况下,由于lazyMan = new LazyMan()不是原子性操作,会出现指令重排的问题。
那么如何解决?
- 分析问题
lazyMan = new LazyMan(); /** * 不是原子性操作,大概概括为以下3步操作: * 1.在栈内存中创建lazyMan变量,在堆内存中开辟一块空间分配给LazyMan实例对象,该空间会得到一个随机地址假设为0x0001; * 2.执行构造方法,初始化LazyMan实例对象; * 3.将lazyMan变量指向该实例对象,即将该对象的地址0x0001赋值给lazyMan变量。 */
因此,CPU执行时,为了提高程序运行效率,会随机打乱部分指令执行的顺序,因此,对于lazyMan = new LazyMan();当CPU执行时,是无法保证一定是按照123执行的,也有可能以132执行。
假如线程A以132的顺序执行;此时线程B执行getInstance(),会认为lazyMan!=null,就会直接return lazyMan,但此时lazyMan还没有完成构造!这时程序也就出现了问题,即指令重排问题。
- 解决:用volatile关键词修饰lazyMan变量,可以解决指令重排问题!
//DCL懒汉式单例模式 + volatile禁止指令重排 public class LazyMan { private LazyMan(){ System.out.println(Thread.currentThread().getName()+" ok"); } //volatile可以解决指令重排问题 private volatile static LazyMan lazyMan; public static LazyMan getInstance(){ if(lazyMan == null){ synchronized(LazyMan.class){ if(lazyMan == null){ lazyMan = new LazyMan(); //不是原子性操作,会导致指令重排问题 } } } return lazyMan; } }
- 为什么volatile可以解决这个问题?
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作 !
-
4. 静态内部类实现单例模式
//静态内部类实现单例模式
public class Holder {
private Holder(){}
private static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
- 但是这都不安全!因为反射!
5. 反射破坏单例模式
第1种破坏:
import java.lang.reflect.Constructor;
public class LazyMan {
private LazyMan(){}
private volatile static LazyMan lazyMan;
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();
//利用反射创建对象instance2,破坏了单例模式
Constructor<LazyMan> lazyManConstructor = LazyMan.class.getDeclaredConstructor(null);
lazyManConstructor.setAccessible(true); //无视了private构造器
LazyMan instance2 = lazyManConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
- 运行结果:
com.anobabe.single.LazyMan@1b6d3586
com.anobabe.single.LazyMan@4554617c
- 解决:通过在构造方法种加锁,防止这种反射破坏!
import java.lang.reflect.Constructor;
public class LazyMan {
private LazyMan(){
synchronized (LazyMan.class){
if(lazyMan != null){
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
private volatile static LazyMan lazyMan;
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();
//利用反射创建对象instance2,破坏了单例模式
Constructor<LazyMan> lazyManConstructor = LazyMan.class.getDeclaredConstructor(null);
lazyManConstructor.setAccessible(true); //无视了private构造器
LazyMan instance2 = lazyManConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
- 运行结果
...
Caused by: java.lang.RuntimeException: 不要试图使用反射破坏异常
at com.anobabe.single.LazyMan.<init>(LazyMan.java:11)
... 5 more
第2种破坏:
- 两个实例都通过反射来new!
import java.lang.reflect.Constructor;
public class LazyMan {
private LazyMan(){
synchronized (LazyMan.class){
if(lazyMan != null){
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
private volatile static LazyMan lazyMan;
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 {
//利用反射同时创建对象instance1、instance2,破坏单例模式
Constructor<LazyMan> lazyManConstructor = LazyMan.class.getDeclaredConstructor(null);
lazyManConstructor.setAccessible(true); //无视了private构造器
LazyMan instance1 = lazyManConstructor.newInstance();
LazyMan instance2 = lazyManConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
- 运行结果
com.anobabe.single.LazyMan@1b6d3586
com.anobabe.single.LazyMan@4554617c
- 解决:利用红绿灯防止这种反射的破坏!
package com.anobabe.single;
//懒汉式单例
import java.lang.reflect.Constructor;
public class LazyMan {
//红绿灯
private static boolean anoBabe = false;
private LazyMan(){
synchronized (LazyMan.class){
if(anoBabe == false){
anoBabe = true;
}else{
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
private volatile static LazyMan lazyMan;
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 {
//利用反射同时创建对象instance1、instance2,破坏单例模式
Constructor<LazyMan> lazyManConstructor = LazyMan.class.getDeclaredConstructor(null);
lazyManConstructor.setAccessible(true); //无视了private构造器
LazyMan instance1 = lazyManConstructor.newInstance();
LazyMan instance2 = lazyManConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
- 运行结果:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at com.anobabe.single.LazyMan.main(LazyMan.java:50)
Caused by: java.lang.RuntimeException: 不要试图使用反射破坏异常
at com.anobabe.single.LazyMan.<init>(LazyMan.java:17)
... 5 more
第3种破坏:
- 通过反编译,假设知道了变量anoBabe
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class LazyMan {
private static boolean anoBabe = false;
private LazyMan(){
synchronized (LazyMan.class){
if(anoBabe == false){
anoBabe = true;
}else{
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
private volatile static LazyMan lazyMan;
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 {
Field anoBabe = LazyMan.class.getDeclaredField("anoBabe");
anoBabe.setAccessible(true);
Constructor<LazyMan> lazyManConstructor = LazyMan.class.getDeclaredConstructor(null);
lazyManConstructor.setAccessible(true); //无视了private构造器
LazyMan instance1 = lazyManConstructor.newInstance();
anoBabe.set(instance1,false);
LazyMan instance2 = lazyManConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
- 运行结果:
com.anobabe.single.LazyMan@4554617c
com.anobabe.single.LazyMan@74a14482
道高一尺魔高一丈!
那么到底如何解决呢?
- newInstance源码:
6. 枚举类实现单例模式
//枚举 enum :本身就是一个类
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) {
EnumSingleton instance1 = EnumSingleton.INSTANCE;
EnumSingleton instance2 = EnumSingleton.INSTANCE;
System.out.println(instance1);
System.out.println(instance2);
}
}
- 运行结果:
INSTANCE
INSTANCE
尝试破坏!
- 先分析该枚举类单例模式源码是有参构造还是无参构造?
import java.lang.reflect.Constructor;
//枚举类单例模式!
//枚举 enum :本身就是一个类
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingleton instance1 = EnumSingleton.INSTANCE;
//EnumSingleton instance2 = EnumSingleton.INSTANCE;
//通过反射创建instance2
Constructor<EnumSingleton> enumSingletonConstructor = EnumSingleton.class.getDeclaredConstructor(null);
enumSingletonConstructor.setAccessible(true);
EnumSingleton instance2 = enumSingletonConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
//java.lang.NoSuchMethodException: com.anobabe.single.EnumSingleton.<init>()
- 运行结果:不是我们想要的"Cannot reflectively create enum objects"
Exception in thread "main" java.lang.NoSuchMethodException: com.anobabe.single.EnumSingleton.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.anobabe.single.Test.main(EnumSingleton.java:17)
- 通过javap反编译,还是可以看到有该无参构造啊!
- 用jad 反编译生成java文件
- 所以,应该将原来的无参改成有参构造:
import java.lang.reflect.Constructor;
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingleton instance1 = EnumSingleton.INSTANCE;
//用有参构造!
Constructor<EnumSingleton> enumSingletonConstructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);
enumSingletonConstructor.setAccessible(true);
EnumSingleton instance2 = enumSingletonConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
运行结果:是我们想要的!“Cannot reflectively create enum objects”
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.anobabe.single.Test.main(EnumSingleton.java:19)
枚举类单例模式确实不能被反射破坏!!!