单例模式是指使某个类只被实例化一次,其余位置再次调用初始化函数会返回之前生成的第一个实例的引用。
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; } }
懒汉模式不能用这种