设计模式-单例设计模式

1. 懒汉式

概念:使用的时候才初始化,jvm只有一个实例。

public class LazyMan { 
    public LazyMan() { 
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    public 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 < 300; i++) { 
            new Thread(() -> getInstance()).start();
        }
    }
}

会出现:

可见并没有实现单例模式。

1.1实现方式1

如果在多线程下使用:<mark>必须加上synchronized才可以保证只有一个对象被创建</mark>

public class LazySingletonTest { 
    public static void main(String[] args) { 
        new Thread(()->{ 
            LazySingleton lazySingleton1 = LazySingleton.getInstance();
            System.out.println(lazySingleton1);
        }).start();

        new Thread(()->{ 
            LazySingleton lazySingleton2 = LazySingleton.getInstance();
            System.out.println(lazySingleton2);
        }).start();
    }
}

class LazySingleton{ 
    private static  LazySingleton lazySingleton = null;
    //构造器私有
    private LazySingleton(){ }

    //获取实例
    public static synchronized LazySingleton getInstance(){ 
        if( lazySingleton == null ){ 
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

优化LazySingleton类,如果我们将synchronized加在了静态方法上,那么锁住的就是整个类,我们可以这样优化:

    //获取实例(优化)
    public static  LazySingleton getInstance(){ 
        if( lazySingleton == null ){ 
            synchronized (LazySingleton.class){ 
                if( lazySingleton == null )
                lazySingleton = new LazySingleton();
            }
        }
        return lazySingleton;
    }

分析

如果线程1先进入同步代码块后,线程2在外面等候,当线程1完成对象创建后,此时lazySingleton对象已经不为空,就不会再创建lazySingleton对象了,实现了单例设计,优化了锁的范围,提高了效率。
优化引用变量
在我们进行多线程编程时,会出现new出来的变量为空,导致xxx线程的对象是空的,引发空指针异常,为什么会出现这个情况呢?出现这个问题很重要的原因是JVM虚拟机会对一些指令进行重排序,例如:

正常的顺序:
	//我们new 一个对象
	Demo demo = new Demo();
	//可以使用javap命令对class字节码文件进行反汇编,创建Demo对象具体指令
	0:new      #2                            1.分配空间---->返回一个指向该空间的内存引用
	3:dup									  2.堆栈的初始化 
	4:invokespecial	 #3				      3.对分配的空间进行初始化
	7:astore_1 						       4.把内存引用赋值给demo变量
重排序(就是顺序可能会乱掉):
	1.分配空间---->返回一个指向该空间的内存引用
    2.堆栈的初始化 	
    4.把内存引用赋值给demo变量
    3.对空间进行初始化

为了避免这一情况的出现,可以使用volatile关键字修饰引用变量

private static  volatile LazySingleton lazySingleton = null;

作用就是可以防止JVM指令重排。

<mark>使用注意点</mark>

  1. 需要保证线程安全(synchronized)
  2. 双重检查优化synchronized
  3. 防止指令重排

1.2 实现方式2

  • 使用静态内部类实现懒加载
  • 主要依据:静态内部类调用的时候才加载
class LazySingleton2{ 
    //构造器私有
    private LazySingleton2(){ }
    //静态内部类
    private static  class InnerClass{ 
        private  static LazySingleton2 lazySingleton2 = new LazySingleton2();
    }
    //获取对象
    public static LazySingleton2 getInstance(){ 
        return InnerClass.lazySingleton2;
    }
}

但是,需要注意反射攻击,所谓反射攻击,就是在new完一个对象后,还可以通过反射来再创建一个对象,这在单例模式里是不被允许的。
演示反射攻击:

/** * 通过反射攻击来影响LazySingletonTest2类的单例模式 */
public class ReflectAct { 
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { 
        Constructor<LazySingleton2> constructor = LazySingleton2.class.getDeclaredConstructor();
        constructor.setAccessible(true);  //设置访问权限
        LazySingleton2 lazySingleton2 = constructor.newInstance();
        LazySingleton2 instance = LazySingleton2.getInstance();
        System.out.println(lazySingleton2 == instance);   //false

    }
}

可见,反射确实可以干预单例模式,解决方法:<mark>修改私有构造</mark>

//获取对象
public static LazySingleton2 getInstance(){ 
    if(InnerClass.lazySingleton2 != null) { 
        throw new RuntimeException("单例模式不允许创建多个实例...");
    }
    return InnerClass.lazySingleton2;
}

补充:在枚举类型的Class类中已经进行了处理,所以枚举类型可以防止反射工具。

2. 饿汉式

概念:饿汉式加载当类一加载完就创建对象
类加载步骤:

  1. 加载对应的二进制文件进方法区,创建对应的数据结构
  2. 连接:a.验证 b.准备(默认值的赋值) c.解析
  3. 初始化:给静态变量赋值
public class HungrySingletonTest { 
    public static void main(String[] args) { 
        
    }
}
class HungrySingleton{ 
    private static  HungrySingleton hungrySingleton = new HungrySingleton();
    private HungrySingleton(){ }

    public static HungrySingleton getInstance(){ 
        return hungrySingleton;
    }
}