整理JAVA领域的单例设计模式

单例设计模式在程序设计里面被广泛使用,单例的实现方式也各种各样。

单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并
提供一个全局访问点。单例模式是创建型模式。单例模式在现实生活中应用也非常广泛。
例如,国家主席、公司 CEO、部门经理等。在 J2EE 标准中,ServletContext、
ServletContextConfig 等;在 Spring 框架应用中 ApplicationContext;数据库的连接
池也都是单例形式。
 

* 每一种单例,都需要是否懒加载(考虑效率问题);是否被多线程破坏(线程安全),是否被反射获取破坏,
* 是否被反序列化破坏

备注:我们的设计模式,不一定都符合软件设计所有原则;单例模式就不符合开闭原则。

下面把软件设计原则的基本概念贴图:

设计原则解释

开闭原则

对扩展开放,对修改关闭。

依赖倒置原则

通过抽象使各个类或者模块不相互影响,实现松耦合。

单一职责原则

一个类、接口、方法只做一件事。

接口隔离原则

尽量保证接口的纯洁性,客户端不应该依赖不需要的接口。

迪米特法则

又叫最少知道原则,一个类对其所依赖的类知道得越少越好。

里氏替换原则

子类可以扩展父类的功能但不能改变父类原有的功能。

合成复用原则

尽量使用对象组合、聚合,而不使用继承关系达到代码复用的目的。

1.饿汉式单例

 对每种情况进行分析:

  线程安全:可有效的避免线程安全问题(是类的静态私有全局变量,在JVM层面保证只有一个实例对象在堆中,方法区是静态变量地址指针)。

  懒加载问题:非懒加载的模式,对大量的类加载项目会出现效率的问题。

  是否能被反射破坏:可被反射破坏类的单实例。解决办法:在下面的的代码块中,在类的构造函数中,对存在的实例对象进行非空判断,如果已经存在实例对象,人为抛出异常,不能创建。

       上面是常见处理办法,我认为也可以使用全局的静态变量进行类单例的创建控制,在类全局中定义private static int nun=1;   然后在构造方法中,进行判断,如果是小于1的时候,不允许再次

        创建对象,人为抛出异常,因为在第一次创建后程序对变量num-- 操作。

  是否被反序列化破坏:会被序列化破坏  ,反序列化的时候,生成的对象完全是另外一个不同的对象,这是违背了单实例的设计。反序列化的解决办法的原理,在代码的注释地方已经加上。

  解决办法的JDK源码位置:

  ObjectStreamClass()方法中给 readResolveMethod 进行赋值,来看代码:
  readResolveMethod = getInheritableMethod ( cl , "readResolve" , null, Object. class ) ;
 
package com.venus.ioc.singleton;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 测试饿汉式单例模式
 */
public class SingletonMode implements Serializable {
    private static final SingletonMode singletonMode;
    static {
        singletonMode=new SingletonMode();
    }

    /**
     * 解决反序列化文件时,无法获取到和原始对象一致的对象;
     * 造成原因:反序列化时,使用反射再次调用构造方法生成不同的对象。
     * 为啥这个方法可以解决问题?
     * 解答:在反序列化时,会有判断逻辑,如果对象含有此方法,就会直接获取此方法返回对象,不再构造新实例
     */
    private Object readResolve(){
        return singletonMode;
    }
    private SingletonMode(){
        /**
         * 这是解决反射获取单实例的漏洞,在jvm层面没有解决或者说避免,需要程序避免
         */
        if(singletonMode!=null){
            throw new RuntimeException("单实例已经存在,不能再次创建");
        }
    }
    public static SingletonMode getSingletonMode(){
        return singletonMode;
    }

    public static void main(String[] args) throws IllegalAccessException, InstantiationException,
            NoSuchMethodException, InvocationTargetException {
 /*       SingletonMode singletonMode1 = SingletonMode.getSingletonMode();
        SingletonMode singletonMode2 = SingletonMode.getSingletonMode();
        System.out.println(singletonMode1==singletonMode2);
        System.out.println(singletonMode1);
        System.out.println(singletonMode2);*/
        System.out.println("反射获取实例对象-----");

       /* SingletonMode singletonMode3 = SingletonMode.class.newInstance();
        SingletonMode singletonMode4 = SingletonMode.class.newInstance();*/

        Constructor<SingletonMode> declaredConstructor = SingletonMode.class.getDeclaredConstructor(null);
        SingletonMode singletonMode3 = declaredConstructor.newInstance(null);
        SingletonMode singletonMode4 = declaredConstructor.newInstance(null);

        System.out.println(singletonMode3==singletonMode4);
        System.out.println(singletonMode3);
        System.out.println(singletonMode4);
    }
}

下面是另外的一个测试类,测试反序列化的问题;

package com.venus.ioc.singleton;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 测试序列化和反序列化是不同的对象
 */
public class SerializableSingletonMode {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        validateSerializableSingletonMode();
        //validateStaticInnerClassSingletonSerializable();
        //validateEnumSingletonSerializable();
    }
    /**
     * 验证饿汉式单例被反序列化破坏并提供解决方案
     */
    public static void validateSerializableSingletonMode() throws IOException, ClassNotFoundException{
        SingletonMode beforeSingleton=SingletonMode.getSingletonMode();
        SingletonMode singletonMode;

        FileOutputStream fos = new FileOutputStream("C:\\Users\\lj\\Desktop\\serializale.txt");

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fos);

        objectOutputStream.writeObject(beforeSingleton);

        objectOutputStream.close();

        fos.close();

        System.out.println("把对象写入文件--完成");


        FileInputStream fis = new FileInputStream("C:\\Users\\lj\\Desktop\\serializale.txt");

        final ObjectInputStream objectInputStream = new ObjectInputStream(fis);

        singletonMode=(SingletonMode)objectInputStream.readObject();
        objectInputStream.close();
        fis.close();
        System.out.println("从文件中读取序列化文件");
        System.out.println(beforeSingleton==singletonMode);

        System.out.println(beforeSingleton.hashCode());

        System.out.println(singletonMode.hashCode());
    }
}

反序列化测试结果:
在无代码解决前的结果:


把对象写入文件--完成
从文件中读取序列化文件
false
500977346
200006406


代码解决后:在单例类中加载这个方法:在上面的代码里面已经加上。
  private Object readResolve(){
        return singletonMode;
  }

测试结果:

把对象写入文件--完成
从文件中读取序列化文件
true
500977346
500977346

2.懒汉式单例模式

   线程安全:此模式只有在double-check双锁检测机制下面才是线程安全的写法,并且建议加上volatile  关键字(阿里代码规范建议);原因是在jvm或者编译器或者内存执行时,

  会被选择性的指令重排(有内部算法),一旦出现这种情况(非常少见),会导致线程安全问题。

   是否懒加载:是懒加载的

   反射是否影响:反射会有影响,解决办法和饿汉式一致

   反序列化影响:序列化也有影响,解决办法和饿汉式一致

   代码:

package com.venus.ioc.singleton;

/**
 * 懒汉式加载单实例
 */
public class LazySongletonMode {
    private LazySongletonMode(){}
    private static volatile LazySongletonMode lazySongletonMode=null;

    public static LazySongletonMode getInstance(){

        if(lazySongletonMode==null){
            synchronized (LazySongletonMode.class){
                lazySongletonMode=new LazySongletonMode();
            }
        }
        return lazySongletonMode;
    }

    public static void main(String[] args) throws InterruptedException {
        final LazySongletonModeThread lazySongletonModeThread = new LazySongletonModeThread();
        Thread t1 = new Thread(lazySongletonModeThread,"线程01");
        Thread t2 = new Thread(lazySongletonModeThread,"线程02");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("执行完成-----");
    }
}

多线程的测试代码:
package com.venus.ioc.singleton;

import lombok.SneakyThrows;

/**
 * 使用多线程获取懒加载实例
 */
public class LazySongletonModeThread implements Runnable{
    @SneakyThrows
    @Override
    public void run() {
        LazySongletonMode instance = LazySongletonMode.getInstance();
        System.out.println(Thread.currentThread().getName()+"--"+instance);
    }
}

3.静态内部类的方式:

   线程安全:是线程安全的,首先内部类不会被外部类加载时初始化,在被调用时,才初始化内部类的静态变量,使用static 关键字,在jvm层面保证实例独有,和饿汉式一致的原理。

   是否懒加载:是懒加载的

   反射是否影响:反射会有影响,解决办法和饿汉式一致

   反序列化影响:序列化也有影响,解决办法和饿汉式一致

   备注:在此案例中,还写了复制对象后是否破坏单例的用法,测试发现,仅浅复制都会变成另外一个实例对象,和原始对象不同,详情见下面的代码和注释。必须实现Cloneable。

   序列化和反序列化时,出现了transient关键字的使用,虽然代码有解释,这里再次备注下;只使用在变量前面修饰,这个关键字保证对象的变量不会被序列化到文件或者其他流文件中,

    在反序列化时,不会获    取到原始的值,获取的是null;对于静态变量不会起到效果,是因为静态变量是类层面的变量,加上这个关键字,序列化和反序列化不影响。

    实例必须实现Serializable接口,不然会抛出异常。

    这里需要关注序列化接口的另外一个接口 Externalizable,这不是一个标记接口,需要实现此接口的两个方法,序列化的对象和属性的序列化和反序列化时和transient 无关,和实现的方法写法有关。

    代码:

package com.venus.ioc.singleton;

import org.springframework.beans.BeanUtils;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import static org.aspectj.weaver.ResolvedTypeMunger.InnerClass;

/**
 * 静态内部类实现单例模式;
 *
 * 重点理解,执行的时序图
 *
 * 外部类实例化后并不会实例化内部类,只有当调用时,才会实例化静态内部类,进而实话innerClassSingleton对象,
 * innerClassSingleton 对象是包含在内部类的一个对象实例。
 *
 * 每一种单例,都需要是否懒加载(考虑效率问题);是否被多线程破坏(线程安全),是否被反射获取破坏,
 * 是否被反序列化破坏
 *
 * 静态内部类在这几个维度的考虑如下:
 *
 * 1.是采用懒加载的方式---效率较好,----属于懒汉式模式扩展
 * 2。是否被多线程获取到不同的实例,在下面main方法多次测试后,发现获取都是同一实例,
 * 原因:类的静态变量存储在方法区,只有一个实例对象,
 */
public class StaticInnerClassSingleton implements Serializable,Cloneable {
    /**
     * 反序列化解决方法
     */
    private Object readResolve(){
        return StaticInnerClassSingleton.InnerClass.innerClassSingleton;
    }

    private  int num=0;
    private StaticInnerClassSingleton(){
        if(StaticInnerClassSingleton.getInstance()!=null){
          throw new RuntimeException("单实例已存在,不允许多次创建");
        }
        System.out.println("生成实例对象");
    }
    private static class InnerClass{
        private static StaticInnerClassSingleton innerClassSingleton=new StaticInnerClassSingleton();
    }
    public static StaticInnerClassSingleton getInstance(){
        System.out.println("获取内部单实例");
        return StaticInnerClassSingleton.InnerClass.innerClassSingleton;
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, CloneNotSupportedException {
        /*System.out.println("验证多线程的获取是否同一实例");
       new Thread(() -> {
            StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
            System.out.println(instance.hashCode());//和验证静态内部类线程2获取的对象hashcode一致
        },"验证静态内部类线程1").start();

        new Thread(() -> {
            StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
            System.out.println(instance.hashCode());
        },"验证静态内部类线程2").start();

        System.out.println("结论==静态内部类方式实现单实例,在多线程情况下是安全的");*/

        /*System.out.println("验证反射是否可以破坏单例模式--");
        final Class<?> aClass = Class.forName("com.venus.ioc.singleton.StaticInnerClassSingleton");
        final Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(null);
        final StaticInnerClassSingleton instance01 = (StaticInnerClassSingleton)declaredConstructor.newInstance(null);
        final StaticInnerClassSingleton instance02 = (StaticInnerClassSingleton)declaredConstructor.newInstance(null);

        System.out.println(instance01==instance02);//false
        System.out.println(instance01.hashCode());//960604060
        System.out.println(instance02.hashCode());//1349393271

        System.out.println("结论:==反射可破坏单实例的创建,两次获取的是不同的对象");*/
        //防止反射获取到不同的对象,普遍采用在构造函数执行时,进行异常的抛出。相当于阻止了反射方式的调用。

        System.out.println("验证复制方法是否破坏单例");
        StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
        StaticInnerClassSingleton clone01 = (StaticInnerClassSingleton)instance.clone();
        System.out.println(instance==clone01);
        clone01.num=1;
        System.out.println(instance.hashCode()+"=成员变量复制前="+instance.num);//0
        System.out.println(clone01.hashCode()+"=成员变量复制后="+clone01.num);//1
        /**
         *浅克隆是指对象不一样,非引用的变量是全部复制一份到新的对象;深克隆,引用的变量也复制一份,如果引用层次比较多,使用copy方法会很麻烦
         * 如果对象含有引用对象,需要实现深克隆,必须实体实现Cloneable接口,只是标记,不然会抛出异常,引用的对象也必须使用copy再次复制一遍,才会
         * 把原始的引用对象进行复制。
         *
         * 实现克隆技术,主要是三种方式:
         * 1.object copy 方法,,
         * 2.spring 或者apache common 包提供的BeanUtils.copyProperties(stu2,stu1);  PropertyUtils  这两个工具类
         * 3.使用序列化方式,序列化后是对原始对象(包含所有引用对象)的复制进去文件,然后再次从文件读取实体,就是两个完全不同的对象。
         * 序列化需要使用Serializable标记,不然抛出异常。
         */
        System.out.println("证明copy 后的对象是和原始的对象是不同的,这里只是浅克隆,不是深克隆");

    }
}

4.枚举实现单例

线程安全:是线程安全的,因为在被定义枚举时,默认生成的字节码文件继承了Enum类,定义的变量instance 在静态代码块已被初始化了,并且编译后是private static String instance;这种形式

所以,在jvm层面保证枚举的实例一定是独有,不可能存在第二个实例。

效率问题:枚举在初始化时,就及时加载对象,不是懒加载。

反射是否破坏:不会被破坏。因为不能被反射获取对象,JDK源码里面限制了如果是枚举类型不能使用反射实例化。

代码:

package com.venus.ioc.singleton;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.CountDownLatch;

/**
 * 定义枚举,枚举单例是effect-java 书籍推荐的单例实现
 *
 * 为啥
 */
public enum EnumSingleton {
    instance;
    private User user;
    private EnumSingleton(){
        System.out.println("执行枚举单例");
    }
    public void setUser(User user) {
        this.user = user;
    }

    public User getUser() {
        return user;
    }

    public static void main(String[] args) throws IllegalAccessException, InstantiationException, InterruptedException, InvocationTargetException, NoSuchMethodException {
        /*System.out.println("验证多线程是否破坏枚举单例");
        new Thread(() -> {
            User user =EnumSingleton.instance.user();
            System.out.println(user.hashCode());
        },"验证枚举单例线程1").start();

        new Thread(() -> {
            User user =EnumSingleton.instance.user();
            System.out.println(user.hashCode());
        },"验证枚举单例线程2").start();

        Thread.sleep(100);
        System.out.println("ok,多线程多次获取是同一对象");*/

        System.out.println("----------反射获取实例");

        Constructor<User> declaredConstructor = User.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        final User user01 = declaredConstructor.newInstance(null);

        System.out.println(user01.hashCode());

        final User user02 =declaredConstructor.newInstance(null);

        System.out.println(user01==user02);

        System.out.println(user02.hashCode());
        System.out.println("测试结果:不能获取到单例对象");

    }


    static class User implements Serializable {
        //Externalizable
        public volatile static int count=1;
        User(){
            if(count<1){
             throw new RuntimeException("已经实例化");
            }
            count--;
        }
        private  String username;

        public void setUsername(String username) {
            this.username = username;
        }

        public String getUsername() {
            return username;
        }
    }
}


枚举序列化测试:
package com.venus.ioc.singleton;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 测试序列化和反序列化是不同的对象
 */
public class SerializableSingletonMode {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //validateSerializableSingletonMode();
        //validateStaticInnerClassSingletonSerializable();
        validateEnumSingletonSerializable();
    }
  /***
     * 验证枚举的反序列化问题
     */
    private static void validateEnumSingletonSerializable() throws IOException, ClassNotFoundException {
        EnumSingleton enumSingleton01 = EnumSingleton.instance;
        enumSingleton01.setUser(new EnumSingleton.User());
        EnumSingleton enumSingleton02;

        FileOutputStream fos = new FileOutputStream("C:\\Users\\lj\\Desktop\\serializale.txt");

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fos);

        objectOutputStream.writeObject(enumSingleton01);

        objectOutputStream.close();

        fos.close();

        System.out.println("把对象写入文件--完成");


        FileInputStream fis = new FileInputStream("C:\\Users\\lj\\Desktop\\serializale.txt");

        final ObjectInputStream objectInputStream = new ObjectInputStream(fis);

        enumSingleton02=(EnumSingleton)objectInputStream.readObject();

        objectInputStream.close();

        fis.close();

        System.out.println("从文件中读取序列化文件");
        System.out.println(enumSingleton01.getUser()==enumSingleton02.getUser());

        System.out.println(enumSingleton01.getUser());

        System.out.println(enumSingleton02.getUser());
    }
}


习惯性地想来看看 JDK 源码,进入 Constructor 的 newInstance()方法:会找到如下的判断:
if ((clazz.getModifiers() & Modifier.ENUM) != 0) {
   throw new IllegalArgumentException("Cannot reflectively create enum objects");
}

5.  Spring ioc 容器的单例

不讨论安全性等问题,只是简化spring初始模式方式:

代码:

package com.venus.ioc.singleton;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 这里模拟Spring ioc 容器的单例
 */
public class ContainerSingleton {

    private static Map<String,ContainerSingleton> singeMap=new ConcurrentHashMap<>(2);

    private ContainerSingleton(){}

    public static ContainerSingleton getInstance(){
        if(singeMap.size()==0){
            synchronized (ContainerSingleton.class){
                if(singeMap.size()==0){
                    singeMap.put("containerSingleton",new ContainerSingleton());
                }
            }
        }
        return singeMap.get("containerSingleton");
    }

    public static void main(String[] args) {
        //测试容器式单例(单线程测试)
        final ContainerSingleton instance01 = ContainerSingleton.getInstance();
        final ContainerSingleton instance02 = ContainerSingleton.getInstance();

        System.out.println(instance01==instance02);
        System.out.println(instance01.hashCode());
        System.out.println(instance02.hashCode());
        System.out.println("多线程测试容器单例-----");
        new Thread(() -> {
            ContainerSingleton instance03 = ContainerSingleton.getInstance();
            System.out.println(instance03.hashCode());
        }, "测试容器单例线程1").start();

        new Thread(() -> {
            ContainerSingleton instance03 = ContainerSingleton.getInstance();
            System.out.println(instance03.hashCode());
        }, "测试容器单例线程2").start();
    }
}

6.线程变量类实现伪单例

代码:

package com.venus.ioc.singleton;

/**
 * 线程变量类实现伪单例
 */
public class ThreadLocalSingleton {

     public  String username;
     private ThreadLocalSingleton(){}
     private static ThreadLocal<ThreadLocalSingleton> threadLocal=new ThreadLocal<>();

     public static ThreadLocalSingleton getInstance(){
         ThreadLocalSingleton threadLocalSingleton = threadLocal.get();
         if(threadLocalSingleton==null){
             synchronized (ThreadLocalSingleton.class){
                 if(threadLocalSingleton==null){
                     threadLocal.set(new ThreadLocalSingleton());
                 }
             }
         }
         return threadLocal.get();
     }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            ThreadLocalSingleton instance03 = ThreadLocalSingleton.getInstance();

            instance03.username="test03";

            System.out.println(instance03.hashCode());
        }, "测试ThreadLocal单例线程1").start();

        new Thread(() -> {
            ThreadLocalSingleton instance03 = ThreadLocalSingleton.getInstance();

            System.out.println(instance03.username);

            System.out.println(instance03.hashCode());

        }, "测试ThreadLocal单例线程2").start();

        Thread.sleep(100);
        System.out.println("测试单线程---");
        ThreadLocalSingleton instance01 = ThreadLocalSingleton.getInstance();
        ThreadLocalSingleton instance02 = ThreadLocalSingleton.getInstance();
        System.out.println(instance01.hashCode());
        System.out.println(instance02.hashCode());
    }
}

以上是根据自己工作学习之余总结的使用经验,便于自己以后复习。转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值