饿汉式,DCL懒汉式,深究
饿汉式
public class Hungry {
//饿汉式单例
//私有化构造器
private Hungry() {
}
//一开始就创建好实例对象,会造成资源的浪费
private static Hungry hungry = new Hungry();
public Hungry getInstance() {
return hungry;
}
}
懒汉式
public class LazyMan {
private LazyMan() {
}
//加上volatile可以避免创建对象时不是原子性的问题
//使用volatile禁止指令重排序
private volatile static LazyMan lazyMan;
//双重检测锁模式,懒汉式单例,DCL懒汉式
public static LazyMan getInstance() {
//第一个if判断是为了防止不必要的操作,为了性能优化,不是必要的,但他可以在p已经实例化后避免线程再进入临界区,提升性能。
if (lazyMan == null) {
//临界区
synchronized (LazyMan.class) {
//第二个if是必要的,如果没有,多个线程同时进入临界区就会导致系统中存在多个实例
if (lazyMan == null) {
lazyMan = new LazyMan();//不是原子性
/**
为什么需要使用volatile?
* 对象创建过程,粗略的分为一下三个过程(便于理解)
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向空间
* 我们希望按123执行
* 但是可能会出现不是123的,例如132
* 当线程A执行132时,恰好执行万了3还没有执行2,此时来了线程B认为lazyMan不是null了,直接返回lazyMan,就会出现问题
* 所以需要给lazyMan加上volatile
*/
}
}
}
return lazyMan;
}
}
内部类
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerHolder.holder;
}
private static class InnerHolder{
private static Holder holder= new Holder();
}
}
单例不安全,因为反射
public static void main(String[] args) throws Exception{
Constructor<LazyManExpand> declaredConstructor = LazyManExpand.class.getDeclaredConstructor(null);
//设置可访问
declaredConstructor.setAccessible(true);
LazyManExpand instance1 = lazyManExpand.getInstance();
LazyManExpand instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
xiao相当于一个密钥,但是再复杂的密钥也可能破解,一旦知道密钥的组成,即可再次使用反射破解字段,将密钥再次修改为false
即可破坏单例
private static boolean xiao = false;
private LazyManExpand() {
synchronized (LazyManExpand.class) {
if (xiao == false) {
xiao = true;
} else {
throw new RuntimeException("不要试图使用反射破坏!");
}
}
}
再安全的密钥也有可能被破解
枚举
枚举是最安全的单例
idea看到枚举中的无参构造是假的,枚举其实是有一个(String s,int i)的有参构造