只要是单例模式一定要构造器私有
1.饿汉模式
package com.mahui.single;
//饿汉式单例 可能会浪费资源
public class HungryDemo {
//构造器私有
private HungryDemo(){
}
//饿汉式一上来先把这个对象加载
private final static HungryDemo HUNGRY_DEMO =new HungryDemo();
public static HungryDemo getInstance(){
return HUNGRY_DEMO;
}
}
2.懒汉模式:
1.需要双重检测锁
2.需要用volatile进行修饰对象
package com.mahui.single;
//懒汉式单例
public class LazyManDemo {
//构造器私有
private LazyManDemo(){
System.out.println(Thread.currentThread().getName()+"ok");
}
//避免进行指令重排 用volatile进行修饰
private volatile static LazyManDemo lazyManDemo;
//这是有问题的在单线程下是可以得在多线程中会出现问题 需要加锁
//双重检测锁模式的懒汉式单例 DCL懒汉式
public static LazyManDemo getInstance(){
if(lazyManDemo==null){
//加锁 在锁中在判断一次即可,锁的是class只有一个
synchronized (LazyManDemo.class){
if(lazyManDemo==null){
//这个过程在极端情况下是有一定的问题,它不是原子性操作
/*底层是三层操作
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
* 期望进行123执行顺序
* 也可能是132执行顺序
* 若线程A先进来执行的是13还没执行2完成构造
*这时线程B进来了,以为已经完成构造直接返回lazyManDemo但是其仍为null
*底层会进行指令重排
* 为了避免指令重拍必须将lazyManDemo用volatile进行修饰
*
* */
lazyManDemo=new LazyManDemo();
}
}
}
return lazyManDemo;
}
}
第二个问题:
通过反射可以破坏构造器,会创建两个不同的对象
package com.mahui.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class LazyManDemo02{
//构造器私有
private LazyManDemo02(){
System.out.println(Thread.currentThread().getName()+"ok");
}
//避免进行指令重排 用volatile进行修饰
private volatile static LazyManDemo02 lazyManDemo02;
//这是有问题的在单线程下是可以得在多线程中会出现问题 需要加锁
//双重检测锁模式的懒汉式单例 DCL懒汉式
public static LazyManDemo02 getInstance(){
if(lazyManDemo02==null){
//加锁 在锁中在判断一次即可,锁的是class只有一个
synchronized (LazyManDemo.class){
if(lazyManDemo02==null){
//这个过程在极端情况下是有一定的问题,它不是原子性操作
lazyManDemo02=new LazyManDemo02();
}
}
}
return lazyManDemo02;
}
//反射
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
LazyManDemo02 lazyManDemo02 = LazyManDemo02.getInstance();
Constructor<LazyManDemo02> declaredConstructor = LazyManDemo02.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
LazyManDemo02 instance = declaredConstructor.newInstance();
//如果被破坏了这两个对象是不一样的
System.out.println(lazyManDemo02);
System.out.println(instance);
}
}
运行结果:
解决方案:
反射是破坏了构造器,在构造器中添加判断可以解决
private LazyManDemo02(){
//在构造器上加一把锁
synchronized (LazyManDemo02.class){
if(lazyManDemo02!=null){
//如果不为空就抛出异常
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
但是两个对象都是通过反射创建,同样会出现两个对象
解决方案:
设置第三方变量来判断在构造器中判断
private static boolean flag=false;
//构造器私有
private LazyManDemo02(){
//在构造器上加一把锁
synchronized (LazyManDemo02.class){
if(flag == false){
flag=true;
}else {
//如果不为空就抛出异常
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
System.out.println(Thread.currentThread().getName()+"ok");
}
但是可以通过反射来该变字段的值:所以这是一个很大的问题
如何解决这问题,其实可以使用枚举类型。
package com.mahui.single;
import java.lang.reflect.Constructor;
public enum EnumDemo {
INSTACE;
public EnumDemo getInstace(){
return INSTACE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumDemo instance1 = EnumDemo.INSTACE;
//通过反射去获取对象
Constructor<EnumDemo> declaredConstructor = EnumDemo.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumDemo instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
在源码中看到该类有一个无参构造
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.mahui.single;
public enum EnumDemo {
INSTACE;
//源码中是有一个无参构造的
private EnumDemo() {
}
public EnumDemo getInstace() {
return INSTACE;
}
}
但是运行结果显示该类没有无参构造器
java.lang.NoSuchMethodException: com.mahui.single.EnumDemo.()
但是在反射的newInstance()方法中枚举会抛出异常"Cannot reflectively create enum objects"但是并没有抛出异常"Cannot reflectively create enum objects"输出了异常没有空参构造,没有达到预期的效果
分析出现这样的原因:
反编译EnumDemo.class文件
使用jad反编译器将class文件编译为java文件
反编译的java文件
查看其java文件:枚举类型的最终反编译源码
通过有参构造器创建对象传入参数(String s, int i)
package com.mahui.single;
import java.lang.reflect.Constructor;
public enum EnumDemo {
INSTACE;
public EnumDemo getInstace(){
return INSTACE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumDemo instance1 = EnumDemo.INSTACE;
Constructor<EnumDemo> declaredConstructor = EnumDemo.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumDemo instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
运行结果正常抛出异常:不能通过反射创建枚举对象
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
3.静态内部类:
//静态内部类
//不安全的
public class Holder {
//构造器私有
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
} public static class InnerClass{
private static final Holder HOLDER=new Holder();
}
}
总结:
单例不安全,原因是有反射,引入枚举来创建单例