1、单例模式(设计模式笔记)

一、分类

  • 创建型模式:
    单例模式、工厂模式、抽象工厂模式、建造者模式、原形模式

  • 结构型模式:
    适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式

  • 行为型模式:
    模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式

二、单例模式

保证一个类只有一个实例,并且提供一个访问该实例的全局访问点

2.1 常用场景

  • windowsTask Manager(任务管理器)就是很典型的单例模式
  • windows的回收站也是。
  • 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
  • 网站的计数器
  • 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
  • 数据库连接池
  • 操作系统的文件系统
  • application也是单例的典型应用(servlet编程中会涉及到)
  • spring中,每个bean默认就是单例的,这样做的有点是spring容器便于管理
  • servlet编程中,每个servlet也是单例
  • struts1中,控制器对象也是单例

2.2 优点

  • 由于单例模式只是生成了一个实例对象,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过

  • 在应用启动时直接产生一个单例对象,然后永久驻留在内存的方式来解决。

  • 单例模式可以在系统设置全局的访问点,优化共享资源的访问,例如设计一个单例类,负责所有数据表的映射处理。

2.3 常见的五种单例模式实现方式

2.3.1 主要

  • 饿汉式(线程安全,调用效率高,但是,不能延时加载)SingletonDemo01
package cn.itcast.day226.singleton;
//饿汉式
public class SingletonDemo01 {
    
    //提供静态属性,这里就不管后面会不会用到,初始化时就加载一个实例
    private static SingletonDemo01 instance = new SingletonDemo01();
    
    private SingletonDemo01(){}//构造器私有化
    
    //这里不需要使用同步,因为加载类的时候(就是在加载上面的属性时)本身就是线程安全的了
    //同时这里没有同步,当然调用效率就高了
    public static /*synchronized*/ SingletonDemo01 getInstance(){
        return instance;
    }
}
  • 懒汉式(线程安全,效率不高,但是可以延时加载)SingletonDemo02
package cn.itcast.day226.singleton;
//懒汉式
/*
 * 用的时候才会加载,资源利用率提高了,但每次调用都需要同步,调用效率就低了
 * */
public class SingletonDemo02 {
    private static SingletonDemo02 instance ;
    
    private SingletonDemo02(){}
    
    public static synchronized SingletonDemo02 getInstance(){
        if(instance == null){
            instance = new SingletonDemo02();
        }
        return instance;
    }
}

2.3.2 其他

  • 双重检测锁式(由于jvm底层内部模型原因,偶尔会出现问题,不建议使用)SingletonDemo03
package cn.itcast.day226.singleton;
//双重检测锁模式,不建议使用
public class SingletonDemo03 {
    private static SingletonDemo03 instance = null;
    private SingletonDemo03(){}
    
    public static SingletonDemo03 getInstance(){
        //之前在懒汉式中我们是将方法同步了,但是这里我们在方法一开始不进行同步
        //而只有在第一次加载的时候发现对象没有实例化才同步
        if(instance == null){
            SingletonDemo03 sc ;
            synchronized (SingletonDemo03.class) {
                sc = instance;
                if(sc == null){
                    synchronized (SingletonDemo03.class) {
                        if(sc == null){
                            sc = new SingletonDemo03();
                        }
                    }
                    instance = sc ;
                }
            }
        }
        return instance;
    }
}
  • 静态内部类式(线程安全,调用效率不高,但是可以延时加载)SingletonDemo04
package cn.itcast.day226.singleton;
//静态内部类模式(其实也是一种懒加载方式)
/*
 * 外部类没有static属性,则不会像饿汉式那样立即加载对象
 * 只有真正调用getInstance方法才会加载静态内部类。加载类时是线程安全的。instance是static final
 * 类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性
 * 兼备了并发高效调用和延时加载的优势
 * */
public class SingletonDemo04 {
    
    private SingletonDemo04(){}
    
    private static class SingletonClassInstance {
        private static final SingletonDemo04 instance = new SingletonDemo04();
    }
    
    public static SingletonDemo04 getInstance(){
        return SingletonClassInstance.instance;
    }
}
  • 枚举单例(线程安全,调用效率高,不能延时加载)SingletonDemo05
package cn.itcast.day226.singleton;
//枚举模式,枚举天然就是单例的
/*
 * 实现简单,枚举本身就是单例,由jvm从根本上提供保障。避免通过反射和反序列化的漏洞,但是不能延时加载
 * */
public enum SingletonDemo05 {
    
    INSTANCE;//定义一个枚举元素,它就代表了Singleton的一个实例
    
    //单例可以有自己的操作
    public void singletonOperation(){
        //功能处理
    }
    
    //使用例子
    public static void main(String[] args) {
        SingletonDemo05 sd1 = SingletonDemo05.INSTANCE;
        SingletonDemo05 sd2 = SingletonDemo05.INSTANCE;
        System.out.println(sd1 == sd2);
        sd1.singletonOperation();//调用方法
    }
}

2.4 如何选用

  • 如果单例对象占用资源要少,不需要延时加载:
    使用枚举式好于饿汉式

  • 如果单例对象占用资源大,需要延时加载:
    静态内部类式好于懒汉式

2.5 问题

  • 反射可以破解上面几种方式(除枚举式)
  • 反序列化可以破解上面几种方式(除枚举式)
    • 可以通过定义readResolve()防止获得不同对象
    • 反序列化时,如果对象所在类定义了readResolve(),(实际上是一种回调),定义返回哪个对象
package cn.itcast.day226.singleton;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
/*
 * 测试懒汉式(如何防止反射和反序列化漏洞)
 * */
public class SingletonDemo06 implements Serializable{
    private static SingletonDemo06 instance;

    private SingletonDemo06() {
        //只有在没有实例化对象的时候才能使用构造器
        if(instance != null){
            throw new RuntimeException();//通过抛出异常来防止反射漏洞
        }
    }

    public static synchronized SingletonDemo06 getInstance() {
        if (instance == null) {
            instance = new SingletonDemo06();
        }
        return instance;
    }
    
    //在反序列化时,如果我们定义了这个方法,则会直接调用这个方法返回当前的对象,而不是实例化一个新的对象
    private Object readResolve() throws ObjectStreamException{
        return instance;
    }

    public static void main(String[] args) throws Exception {
        SingletonDemo02 s1 = SingletonDemo02.getInstance();
        SingletonDemo02 s2 = SingletonDemo02.getInstance();
        System.out.println(s1 == s2);

        // 加反射
        Class clazz = (Class) Class
                .forName("cn.itcast.day226.singleton.SingletonDemo02");
        Constructor c = clazz.getDeclaredConstructor(null);// 获得无参构造器
        c.setAccessible(true);// 跳过权限检查
        SingletonDemo02 s3 = c.newInstance();
        SingletonDemo02 s4 = c.newInstance();
        System.out.println(s3 == s4);// 此时两个对象就不想等了
        
        //下面我们看上面改造之后的情况
        Class clazz1 = (Class) Class
                .forName("cn.itcast.day226.singleton.SingletonDemo06");
        Constructor c1 = clazz1.getDeclaredConstructor(null);// 获得无参构造器
        c.setAccessible(true);// 跳过权限检查
        SingletonDemo06 s5 = c1.newInstance();
        SingletonDemo06 s6 = c1.newInstance();
        System.out.println(s5 == s6);// 此时两个对象就不想等了
        
        //加反序列化
        //序列化:将s1对象写到磁盘上
        FileOutputStream fos = new FileOutputStream("d:/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s1);
        oos.close();
        fos.close();
        
        //反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
        SingletonDemo02 s7 = (SingletonDemo02) ois.readObject();
        System.out.println(s1 == s7);
        
        //防止反序列化,定义readResolve方法
        SingletonDemo06 s8 = SingletonDemo06.getInstance();
        //序列化:将s1对象写到磁盘上
        FileOutputStream fos1 = new FileOutputStream("d:/b.txt");
        ObjectOutputStream oos1 = new ObjectOutputStream(fos1);
        oos1.writeObject(s8);
        oos1.close();
        fos1.close();
        
        //反序列化
        ObjectInputStream ois1 = new ObjectInputStream(new FileInputStream("d:/b.txt"));
        SingletonDemo06 s9 = (SingletonDemo06) ois1.readObject();
        System.out.println(s8 == s9);
    }
}

2.6 测试五中模式的效率

  • 饿汉式:22ms
  • 懒汉式:636ms
  • 静态内部类式:28ms
  • 枚举式:32ms
  • 双重检测锁式:65ms
package cn.itcast.day226.singleton;
import java.util.concurrent.CountDownLatch;
//测试多线程环境下五种创建单例模式的效率
public class Client {
    public static void main(String[] args) throws InterruptedException {
        
        long start = System.currentTimeMillis();
        int threadNum = 10;
        final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
        
        for(int i = 0; i < 10; i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int j = 0; j < 100000; j++){
                        Object obj = SingletonDemo01.getInstance();//这里进行检测
                    }
                    //这里在上面定义CountDownLatch的时候需要加final,因为内部类不能直接使用外部的局部变量
                    countDownLatch.countDown();
                }
            }).start();;
        }
        countDownLatch.await();//等待所有线程都执行完
        long end = System.currentTimeMillis();
        //如果不使用CountDownLatch则下面的结果只是main线程的时间,而其他线程其实还没有运行完
        System.out.println(end - start);
    }
}

说明:

  • 这里我们通过CountDownLatch同步辅助类,在完成一组在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
  • countDown():当前线程调用此方法,则计数减一(建议放在finally中)
  • await():调用此方法会一直阻塞当前线程,直到计数器的值为0

你可能感兴趣的:(1、单例模式(设计模式笔记))