Java设计模式-单例模式(全例记录)

    技术2022-07-10  131

    1. 基础介绍

    核心作用:保证一个类只有一个实例,并提供一个访问该实例的全局访问点;优点:由于单例模式只生成一个实例,所以减少了系统的开销,当一个系统启动需要较多的资源时,可以直接在系统启动时产生一个单例对象,然后使其永久驻留内存;单例模式可以在系统设置全局访问点,优化共享资源的访问;两种主要的单例模式实现方式:饿汉式,懒汉式;

    2. 饿汉式介绍

    线程安全,无法延时加载,也就是只要系统一运行,这个单例模式的类对象就会被加载到内存;简单步骤: 首先,将构造器私有化,这样就无法在类的外部直接创建对象; 然后再类中创建一个本类对象(使用private static修饰,做到全局使用); 再定义一个getInstance()方法,用来在其它的类中创建单例对象;public class Singleton { //通过这两个修饰的对象,是线程安全的; private static Singleton singleton = new Singleton(); //创建一个私有的构造方法; private Singleton() { } //定义一个static方法用来在其它类中获得这个类的实例; public static Singleton getInstance() { return singleton; } }

    3. 懒汉式

    线程安全,延时加载,真正使用时创建;调用的效率比较低(因为每一次调用,都会重新创建一个对象);简单步骤: 首先先创建一个空的类实例,使用private static修饰; 然后创建一个私有的构造方法; 最后定义一个加锁的getInstance()方法,用于返回该类的对象;public class SingletonDemo { //创建一个该类的空对象引用; private static SingletonDemo sing; //创建该类的私有构造方法; private SingletonDemo(){} //创建一个getInstance()方法,用于创建该类的对象;这里用个锁,就可以防止创建多个对象同时被创建;当然,锁也可以放到里面去,放外面可能效率更加低; public synchronized static SingletonDemo getInstance(){ //判断sing对象是否为空,为空则创建;反之,直接输出; if(sing==null){ sing = new SingletonDemo(); } return sing; } }

    4. 双重检测锁实现

    上面讲到,可以将synchronized放入到方法里面去,这里就是这样做的,这样可以提高它锁的效率;但是涉及很到jvm底层,目前还没学习,暂做了解; 大致的步骤与上面一致,我就在懒汉式中直接修改了;public class SingletonDemo { //创建一个该类的空对像; private static SingletonDemo sing=null; //创建该类的私有构造方法; private SingletonDemo(){} //创建一个getInstance()方法,用于创建该类的对象; public static SingletonDemo getInstance(){ //判断sing对象是否为空,为空则创建;反之,直接输出; if(sing==null){ //在里面进行加锁 synchronized (SingletonDemo.class) { if(sing==null) sing = new SingletonDemo(); } } return sing; } }

    5. 登记式/静态内部类实现方式

    外部没有static属性,不会像饿汉式一样直接加载对象;只有调用getInstance()方法时加载对象,加载类时,线程安全,因为创建的外部类的对象是static final修饰,导致只有一个实例,只能赋值一次;有着饿汉式的高效调用,懒汉式的延时加载; 基本结构介绍: 首先先定义一个static的静态内部类,在该类中创建一个外部类的对象,并用private static final修饰该对象; 创建一个私有化的外部类构造方法; 最后在外部类中创建一个getInstance方法,用于返回该外部类的对象;public class SingleStatic { //创建一个私有的属性; private String name = "神"; //先创建一个该类的私有构造方法; private SingleStatic() { } //创建一个静态的内部类; private static class SingleInside { //定义一个外部类的实例;在内部类中是可以调用外部类的私有构造器的; private static final SingleStatic sig = new SingleStatic(); //我想看看能不能调用外部类的私有属性,可以,很好, public static String string(){ String name=sig.name; return name; } } //创建一个方法返回该类的对象; public static SingleStatic getInstance() { return SingleInside.sig; } public static String getname(){ return SingleInside.string(); } }

    测试类

    class test { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { SingleStatic aStatic = SingleStatic.getInstance(); System.out.println(aStatic); } }).start(); new Thread(new Runnable() { @Override public void run() { SingleStatic instance = SingleStatic.getInstance(); System.out.println(instance); } }).start(); System.out.println(SingleStatic.getname()); } }

    结果输出(符合单例模式):

    6. 枚举

    通过枚举实现单例模式,枚举本身就是一种单例模式,无延时加载,线程安全,调用效率高;public enum SingleEnum { //这就相当与创建好了; SINGLE_ENUM; }

    7.总结

    很明显,在单例模式中除了枚举的方式之外,其它的方式都可以通过反射的方式,强行获取对象的构造方法;当然也是可以避免的;只需要在创建前再次判断抛出一个异常结束就可以了;还有一些比如反序列化也可以进行破解,这里就不一一记录,感觉这种东西,只要了解,知道怎么规避就可以了,防止反序列化破解的话可以直接使用一个方法 readResolve() ,这个方法原理不是很懂,看别人的博客说是在使用对象流的时候会进行检查,如果有这个方法就会直接返回已有的对象,这个方法的出处没有,仿佛就是自己定义的一样;

    Java杂记

    a) 内部类可以直接调用外部类包括private的成员变量,构造方法;

    b) 而外部类调用内部类需要建立内部类对象,当然,内部类也可以将方法设置为静态的;

    Processed: 0.011, SQL: 9