java设计模式(设计模式介绍/创建型模式)之单列模式

设计模式

  • 创建型模式:
    单列模式/工厂模式/抽象工厂模式/建造者模式/原型模式
  • 结构型模式
    适配器模式/桥接模式/装饰模式/组合模式/外观模式/代理模式
  • 行为型模式
    模板方法模式/命令模式/迭代器模式/观察者模式/中介者模式/备忘录模式/解释器模式/状态模式/策略模式/责任链模式/访问者模式.

1.单列模式

1.核心: 保证一个类只有一个实例,并提供一个访问该实例的全局访问点.

2.创建的应用场景: 
任务管理器/回收站/读取配置文件/网站计数器/数据库连接池/操作系统的文件系统/Servlet编程中(Application对象)/Spring中bean /springmvc或Struts中的控制器对象...

3. 单列模式优点:
 (1)减少系统开销,如读取配置文件..
 (2)可以在系统设置全局的访问点,优化共享资源访问. 
2.创建5种单列模式实现方式
1. 恶汉式(线程安全,调用效率高.但是不能延时加载)
2.懒汉式(线程安全,调用效率不高.但是可以延迟加载)
3.双重检测锁式(由jvm底层内部模型)
4.静态内部类式(线程安全,调用效率高,但是可以延时加载)
4.枚举类(线程安全,调用效率高,不能延时加载)

eg.

1.恶汉式
    package com.zhihui.singleton;
/**
 * 测试恶汉式单例模式
 * @author xzb_l
 * 
 */
public class SingletonDemo1 {

// 私有该对象,初始化给静态属性(类初始化时,立即加载,没有延时加载的优势;加载类的时候,会线程安全)
private static SingletonDemo1 instance = new SingletonDemo1();
// 私有构造器
private SingletonDemo1() {}

/**
 * 提供公共访问方法(不需要使用synchronize关键字,效率高)
 */
public static SingletonDemo1 getInstance() {
    return instance;
}
}
2.懒汉式
/**
 * 测试懒汉式单例模式
 * @author xzb_l
 * 
 */
public class SingletonDemo2 {

// 私有该对象,不初始化延时加载,使用时再创建
private static SingletonDemo2 instance;
// 私有构造器
private SingletonDemo2() {}

/**
 * 提供公共访问方法(方法同步,调用效率低)
 */
public static synchronized SingletonDemo2 getInstance() {
    if (instance == null) {
        instance = new SingletonDemo2();
    }
    return instance;
}
}
双重检测锁实现
/**
 * 双重检测锁
 * @author xzb_l
 * 
 */
public class SingletonDemo3 {

private static SingletonDemo3 instance = null;
// 私有构造器
private SingletonDemo3() {}

/**
 * 只需要在第一次的时候同步
 * @return
 */
public static SingletonDemo3 getInstance() {
    if (instance == null) {
        SingletonDemo3 sc;
        synchronized (SingletonDemo3.class) {
            sc = instance;
            if (sc == null) {
                synchronized (SingletonDemo3.class) {
                    if (sc == null) {
                        sc = new SingletonDemo3();
                    }
                }
            }
            instance = sc;
        }
    }
    return instance;
}

}

静态内部类方式实现(也是一种懒加载)
******


/**
 * 静态内部类实现单利模式
 * 这种方式线程安全,调用效率高,并且实现了延时加载
 * @author xzb_l
 * 
 */
public class SingletonDemo4 {

// 静态内部类
private static class SingletonClassInstance{
    private static final SingletonDemo4 instance = new SingletonDemo4();
}
// 私有构造器
private SingletonDemo4() {}

/**
 * 调用的时候加载
 * @return
 */
public static SingletonDemo4 getInstance() {
    return SingletonClassInstance.instance;
}

}

枚举方式(天然单例)
/**
 * 枚举方式实现单例模式
 * @author xzb_l
 * 
 */
public enum SingletonDemo5 {
    // 这个枚举元素本身就是单例对象  类名.INSTANCE  即可(但是没有懒加载的效果)
    INSTANCE;

/**
 * 可以对该单列做其他操作
 */
public void singletonOperation(){
    // 处理 
}

}

选择
单例对象 占用资源少 ,不需要延时加载;
- 枚举 好于 恶汉式
单例对象 占用资源大,需要延时加载;
- 静态内部类 好于 懒汉式

反射和反序列化漏洞

  • 反射可以破解上面几种实现方式(除枚举方式)

    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;
    
    /**
     * 如何防止反序列化和反射漏洞
     * @author xzb_l
     * 
     */
    public class SingletonDemo6 implements Serializable {
    
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    
    // 静态内部类
    private static class SingletonClassInstance{
        private static final SingletonDemo6 instance = new SingletonDemo6();
    }
    // 私有构造器
    private SingletonDemo6() {
        if (SingletonClassInstance.instance == null) {
            try {
                throw new Exception("防止反射");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
        /**
         * 调用的时候加载
         * @return
         */
        public static SingletonDemo6 getInstance() {
            return SingletonClassInstance.instance;
        }
    
        public static void main(String[] args) throws Exception {
            // 反射方式
            Class<SingletonDemo6> clazz = (Class<SingletonDemo6>) Class.forName("com.zhihui.singleton.SingletonDemo6");
                    Constructor<SingletonDemo6> c = clazz.getDeclaredConstructor(null);
            c.setAccessible(true);// 跳过检查
            SingletonDemo6 newInstance1 = c.newInstance();
            SingletonDemo6 newInstance2 = c.newInstance();
            System.out.println(newInstance1 == newInstance2);
    
            SingletonDemo6 s1 = SingletonDemo6.getInstance();
    
            System.out.println("---------------");
    
            // 序列化方式
            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"));
            SingletonDemo6 s3 = (SingletonDemo6) ois.readObject();
            System.out.println(s1 == s3); // 新的
        }
    
        /**
         * 防止反序列化漏洞
         * 反序列化的时候,直接调用这个方法,返回此对象,而无需把读取的新对象返回.
         * (基于回调,反序列化时该类有此方法则会自动调用该方法)
         */
        private Object readResolve() throws ObjectStreamException {
            return SingletonClassInstance.instance;
        }
    }
    

5中单列模式,在多线程环境下的效率测试

借助类 CountDownLatch类:
同步类在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待
- countDown() 当前线程调用此方法,则计数减一(建议放在finally里执行)
- await() 调用此方法会一直阻塞当前线程,知道计时器的值为0

import java.util.concurrent.CountDownLatch;

/**
 * 测试多线程下单例模式效率
 * @author xzb_l
 *
 */
public class TestClient {

public static void main(String[] args) throws Exception {
    // 开始时间
    long start = System.currentTimeMillis();
    int threadNum = 10;
    // 启动线程计数器
    final CountDownLatch countDownLatch = new CountDownLatch(threadNum);

    for (int i=0; i<threadNum; i++) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                // 调用相关方法
                for(int i = 0; i < 10000; i++) {
                    Object o = SingletonDemo1.getInstance(); // 测试恶汉式
                }
                // 执行完,减一
                countDownLatch .countDown();
            }
        }).start();
    }

    // 主线程等待,直到计数器为0
    countDownLatch.await();
    long end = System.currentTimeMillis();
    System.out.println("总耗时:"+(end-start));
}

}

猜你喜欢

转载自blog.csdn.net/qq_34898847/article/details/82666550
今日推荐