单例模式是指使某个类只被实例化一次,其余位置再次调用初始化函数会返回之前生成的第一个实例的引用。

1、懒汉模式

延迟加载,用到的时候才加载

可能会有以下问题

(1)线程安全问题

(2)double check加锁优化

(3)指令重排,需要使用volatile关键字修饰

class Obj1{//单例类
    private volatile static Obj1 instance;
    private Obj1(){}//私有构造方法避免被主动调用实例化

    public static Obj1 getObj1(){//获取实例调用方法
        if(instance == null){// #a
            synchronized (Obj1.class){//锁优化,本来可以直接锁方法,但是会造成性能下降
                if(instance == null){
                    instance = new Obj1();
                    //JAVA字节码顺序
                    //1、分配空间 2、初始化 3、引用赋值
                    //有的时候2、3顺序可能颠倒,当先执行3时,若其他线程恰好执行到#a处,则会导致返回空指针,因此instance变量需要volatile关键字,保证不会指令重排
                }
            }
        }
        return instance;
    }
}
public class Test1 {
    public static void main(String[] args) {
        //调用测试
        new Thread(() -> {
            Obj1 obj1 = Obj1.getObj1();
            System.out.println(obj1);
        }).start();
        new Thread(() -> {
            Obj1 obj1 = Obj1.getObj1();
            System.out.println(obj1);
        }).start();
    }
}

2、饿汉模式

初始化阶段完成实例的初始化,本质是借助jvm类加载机制

类加载过程

(1)加载二进制数据到内存中,生成对应的class数据结构

(2)连接 a验证 b准备(静态成员变量赋默认值),c解析

(3)初始化:类的静态变量赋初值

只有在真正使用该类时才会触发初始化(当前类是启动类,new,访问静态属性,访问静态方法,反射访问,初始化类的子类 等)

package danli;
class Obj2{
    private static Obj2 instance = new Obj2();//该类初始化时,instance将被直接赋初始值。同时由于类加载线程安全,不需要考虑多线程影响
    private Obj2(){}
    public static Obj2 getInstance(){
        return instance;
    }
}
public class Test2 {

    public static void main(String[] args){
        //测试
        Obj2 o1 = Obj2.getInstance();
        Obj2 o2 = Obj2.getInstance();
        System.out.println(o1==o2);

        //多线程测试
        new Thread(() -> {
            Obj2 obj2 = Obj2.getInstance();
            System.out.println(obj2);
        }).start();
        new Thread(() -> {
            Obj2 obj2 = Obj2.getInstance();
            System.out.println(obj2);
        }).start();
    }
}

3、内部静态类

依靠jvm的类加载机制(用到时初始化),既保证懒加载,又能线程安全

class Obj3{
    private Obj3(){}
    private static class InnerObj3{
        private static Obj3 instance = new Obj3();
    }
    public static Obj3 getInstance(){
        return InnerObj3.instance;
    }
}

4、反射攻击

对以上方式进行反射创建测试

public class Test3 {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<Obj3> declaredConstructor = Obj3.class.getDeclaredConstructor();//获取类的构造函数
        declaredConstructor.setAccessible(true);//变更访问权限
        Obj3 obj3 = declaredConstructor.newInstance();//获得实例

        Obj3 obj31 = Obj3.getInstance();

        System.out.println(obj31==obj3);//false
    }
}

可在单例类中添加一些代码防止产生多例,如内部类中

class Obj3{
    private Obj3(){
        if(InnerObj3.instance!=null){
            throw new RuntimeException("单例不允许多个实例");
        }
    }
    private static class InnerObj3{
        private static Obj3 instance = new Obj3();
    }
    public static Obj3 getInstance(){
        return InnerObj3.instance;
    }
}

懒汉模式不能用这种