zoukankan      html  css  js  c++  java
  • Java设计模式之单利模式(Single Pattern)

    一、为什么使用单利模式

    在讲单利模式之前,我们待想一个问题,我们为什么要使用单利模式呢?
    单利:表面的意思就是一个类只能存在一个实例,那我们什么时候会用到单利模式呢?最常见的有以下几种场景:

    1、Windows的Task Manager(任务管理器)就是很典型的单例模式
    2、项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
    3、数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
    4、在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理 。
    5、在servlet编程中/spring MVC框架,每个Servlet也是单例 /控制器对象也是单例.

    综上所述单利有两个好处:

    1、节省内存
    2、方便管理

    二、单利模式的定义及特点

    单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。

    单例模式有 三个特点:

    1、类只有一个实例对象;
    2、该单例对象必须由单例类自行创建;
    3、类对外提供一个访问该单例的全局访问点;

    三、单利模式的实现方式

    我们实现单利模式主要抓住三个特点:

    1、将构造函数私有化
    2、在类的内部创建实例
    3、提供获取唯一实例的方法

    1、饿汉式实现单利
    该模式的特点是在类加载时没有创建实例,只有第一次调用方法getInstance()时才会创建单利对象,具体代码如下:

    /**
     * 懒汉式单利模式(线程安全,调用效率不高,但是,可以实现延时加载)
     */
    public class SingletonL {
        private static volatile SingletonL instance = null;
    
        private SingletonL() {  //避免类在外部被实例化
    
        }
    
        //创建对象的外部方法
        public static synchronized SingletonL getInstance() {
            if (null == instance) {
                instance = new SingletonL();
            }
            return instance;
        }
    }
    

    说明:如果我们在编写多线程的程序就需要关键字 volatile 和 synchronized,能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例模式的缺点。

    2、饿汉式模式
    该模式的特点在类加载的时候就创建对象,在调用getInstance()是对象已经存在。

    /**
     * 饿汉式单利模式(线程安全,调用效率高,但是,不可以延时加载)
     */
    public class SingletonE implements Serializable {
        //防止被引用
        private static SingletonE instance = new SingletonE();
    
        //防止被实例化
        private SingletonE() {
        }
    
        public static SingletonE getInstance() {
            return instance;
        }
    }
    

    说明饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问 题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。

    3、静态内部类

    该模式的特点是通过一个静态内部类创建实例对象,然后调用getInstance()实现单利,是线程安全的

    /**
     * 静态内部类单利模式(线程安全,调用效率高。 但是,可以延时加载)
     */
    public class SingletonJt {
        private static class staticFactory {
            private  static   final  SingletonJt instance = new SingletonJt();
        }
    
        //防止被实例化
        private SingletonJt() {
    
        }
    
        public static SingletonJt getInstance() {
            return staticFactory.instance;
        }
    }
    

    说明外部类没有static属性,则不会像饿汉式那样立即加载对象,只有真正调用getInstance(),才会加载静态内部类。加载类时是线程 安全的。instance是static final 类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性 。

    4、枚举单利

    /**
     * 枚举单利模式(线程安全,调用效率高,不能延时加载)
     */
    public enum  Singletonenum {
        INSTANCE;
    }
    
    public class Client {
        public static void main(String[] args) {
            Singletonenum singletonenum=Singletonenum.INSTANCE;
            Singletonenum singletonenum1=Singletonenum.INSTANCE;
            System.out.println(singletonenum==singletonenum1);
        }
    }
    

    说明枚举本身就是单利模式,不可以实现延时加载。

    四、单例模式的效率安全问题

    两种方式实现懒汉模式的安全问题:
    1、反射
    2、序列化和反序列化

    通过反射中的setAccessible(true)方法,破解懒汉式单利模式,具体代码如下:

    /**
         * 测试懒汉式单利模式(反射)
         */
        public static void testSingleLFs() throws Exception {
           Class c= Class.forName("com.designpattern.pattern.singletonpattern.SingletonL");
           Constructor constructor=c.getDeclaredConstructor(null);
           constructor.setAccessible(true);
           SingletonL singletonL1=(SingletonL) constructor.newInstance();
           SingletonL singletonL11=(SingletonL) constructor.newInstance();
            System.out.println(singletonL1==singletonL11); //返回为false
        }
    

    那我们如何避免这个问题呢?只需要在在单利模式中加入这段代码即可:即在私有构造器中加一个判断规则即可实现,System.out.println(singletonL1==singletonL11); //返回为true

    private SingletonL() {  //避免类在外部被实例化
            if (null==instance){
                throw new RuntimeException("实例已经存在");
            }
        }
    

    通过序列化和反序列化破解单利模式,思路是先把实例的对象写到一个文件当中,然后在读到程序当中,具体代码如下:

    /**
         * 测试懒汉式单利模式(序列化和反序列化)
         * @param singletonL
         * @throws Exception
         */
        public static void testSingleLxL(SingletonL singletonL) throws Exception {
            ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("D:/a.txt"));
            oos.writeObject(singletonL);
            oos.close();
            ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("D:/a.txt"));
            System.out.println((SingletonL)objectInputStream.readObject());
    
        }
    

    避免这个问题的方式是在懒汉式单利模式中加入一段代码即可:

    //反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象!
        private Object readResolve() throws ObjectStreamException {
            return instance;
        }
    

    单利模式实现方式的效率问题,说明几点:我们需要借助这个类

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

    具体代码如下:

    /**
     * 测试单利模式的效率
     */
    public class ClientTest {
    
        public static void main(String[] args) throws InterruptedException {
    
            long start = System.currentTimeMillis();
            int count = 10;
            final CountDownLatch countDownLatch=new CountDownLatch(count);
            for (int j = 0; j < count; j++) {  //多线程环境下
                new Thread(new Runnable() {
                    public void run() {
                        for (int i = 0; i < 10000; i++) {
                            //测试那种实现方式修改这里即可
                            SingletonL singletonL = SingletonL.getInstance(); //测试懒汉式                
                        }
                        countDownLatch.countDown();
                    }
                }).start();
            }
            countDownLatch.await();	//main线程阻塞,直到计数器变为0,才会继续往下执行!
            long end = System.currentTimeMillis();
            System.out.println("总耗时:"+(end-start));
        }
    }
    

    五、小结

    常见的单利模式实现方式
    主要:
    饿汉式:(线程安全,调用效率高,但是,不可以实现延时加载)
    懒汉式:(线程安全,调用效率不高,但是,可以实现延时加载)
    静态内部类式:(线程安全,调用效率高,但是,可以延时加载)
    枚举式:(线程安全,调用效率高,但是,不可以实现延时加载)
    选用方式:
    单利对象 占用资源少,不需要延时 枚举好于饿汉式
    单利对象 占用资源大需要延时加载,静态内部类好于懒汉式

    每天进步一丢丢

    完成。

  • 相关阅读:
    jar包的MANIFEST.MF文件
    Spring AOP无法拦截Controller中的方法
    强制更新 Maven缓存库
    【转】深入理解Java:注解(Annotation)--注解处理器
    Java-Method类常用方法详解
    MySQL下查看用户和建立用户
    【转】从零开始玩转logback
    【转】Java日志框架:logback详解
    【转】配置不当引起高危漏洞?看加密货币交易所如何正确用Spring Boot Actuaotr框架
    Spring MVC Junit4 单元測试 JunitTest
  • 原文地址:https://www.cnblogs.com/xiaofuzi123456/p/12871395.html
Copyright © 2011-2022 走看看