zoukankan      html  css  js  c++  java
  • Java设计模式----单例模式

    单例模式是开发程序过程中最常见的开发模式之一,很多优秀的框架都是采用单例模式设计的,比如spring容器,默认情况下就是采用单例模式来管理Bean对象的。正是因为单例模式简单而常用,所以很多人开发中会滥用单例模式,一些批评者认为,很多情况下根本没有必要使用单例,而且使用单例模式,就必然会引入一个全局状态,面向对象编程中并不提倡过多地维护全局状态,实际开发时,假如你正在写一个单例,那么首先应该考虑一下,到底有没有必要这样做,如果这个对象并不会频繁创建,那么我觉得就没有必要使用单例模式,当然有些地方不得不强制使用单例,比如GUI中,点击菜单打开一个窗口,再次点击当然不能重复打开了。


    1.单例模式

    单例模式(Singleton Pattern),是一个强制对某class只实例化一次的软件设计模式。

    单例模式对于某些场景比较适用,比如假设配置文件中记录着路由信息,这些信息可以由一个单例来统一读取,这样这些配置信息作为全局状态,就只存在一份,其它业务调用这个单例来获取相应的配置信息即可。这种方式简化了复杂环境下的配置管理。

    单例模式主要解决以下问题:

    • 怎样保证一个class只被实例化一次?
    • 怎样方便的获取这个单实例?
    • 怎样控制一个class的实例化过程?
    • 怎样控制一个class的实例化数量?

    单例模式,通过以下步骤来解决以上问题:

    • 隐藏构造起,即将构造起设置为private权限,使外部对象无法实例化该class;
    • 定义一个public的静态方法,用来获取该class的实例.

    线程安全:

    使用lazy方式实现单例模式时,因为可能涉及多个线程同时创建对象,这个时候要保证只创建一个实例。

    下面将介绍几种常用且安全的单例模式实现方式。


    3.代码实现

    3.1 饿汉模式

    public class Singleton1 {
        private static final Singleton1 INSTANCE = new Singleton1();
    
        private Singleton1(){}
    
        public static Singleton1 getInstance(){
            return INSTANCE;
        }
    
    }

    优点:简单明了,线程安全。

    缺点:加载类的时候即实例化对象,如果长时间不用,未免造成浪费;没有实现Lazy initialization.

    3.2 懒汉模式

    实现Lazy initialization实例化对象,即某一线程调用getInstance()时实例化对象,就不得不考虑线程安全问题,加入多个线程同时调用getInstance()且同时创建了多个实例,就违反了单例模式的目的 ,为了避免线程竞争,必须想办法保证线程安全,同时,还要保证效率,这样,一般就不使用同步方法,因为方法同步后,会使效率大幅度降低,这里使用著名的Double Checked locking模式。

    public class Singleton2 {
        // volatile防止指令重排
        private static volatile Singleton2 INSTANCE;
    
        private Singleton2() { }
    
        public static Singleton2 getInstance() {
            if (null == INSTANCE) {
                synchronized (Singleton2.class) {
                    if (null == INSTANCE) {
                        INSTANCE = new Singleton2();
                    }
                }
            }
            return INSTANCE;
        }
    
    }

    双重校验锁方式,实现了Lazy initilization,在保证线程安全的基础上,也保证了之后每次获取实例的效率。只有第一次实例化对象时,线程会同步等待。

    注意到getInstance()方法中,进行了两次null == INSTANCE判断,这时候因为假设有两个线程同时进入到1....处,其中一个线程进入同步代码块2....处,如果没有再次判断null == INSTANCE, 等到第二个线程进入到2....后,又会创建一个实例,即没有保证只实例一个对象,所以,需要在同步代码块中,再次进行null == INSTANCE判断。

    if (null == INSTANCE) {
                1.....
                synchronized (Singleton2.class) {
                    2......
                    INSTANCE = new Singleton2();
                }
            }

    懒汉模式

    优点:实现lazy initialization,线程安全

    缺点:效率不太高, 代码麻烦 

    3.3 静态内部类方式

    这里利用了Java中内部类的特性之一:只有在使用的时候才会去加载,实现了Lazy initialization,从而实例化INSTANCE, 而且不需要同步线程,大大提升了效率,Java运行环境自动就能保证线程安全,另外final关键字保证实例不会被修改,final修饰的地址,还能提升查找时的效率,这种方式推荐使用。

    public class Singleton3 {
        private Singleton3() { }
    
        private static class SingletonHelper {
            private static final Singleton3 INSTANCE = new Singleton3();
        }
    
        public static Singleton3 getInstance() {
            return SingletonHelper.INSTANCE;
        }
    
    }

    优点:实现lazy initilization,线程安全,效率高

    缺点:无(我没有想到,如果有请大神赐教)

    3.4 使用枚举

    使用枚举的特性,单例模式能非常方便的实现,既能实现lazy initialization,又能保证线程安全,而且效率高。

    public enum Singleton4 {
        INSTANCE,
        ;
    }

    反编译Singleton4之后,我们发现由编译器自动生成的class中,同样有一个static final Singleton4 INSTANCE; 类似于内部类的实现,保证了效率,同时,枚举类内部实现也天然保证线程安全,所以,枚举方式也是非常推荐是哟昂的方法,实现起来更加方便。

    public final class Singleton4 extends java.lang.Enum<Singleton4> {
      public static final Singleton4 INSTANCE;
      public static Singleton4[] values();
      public static Singleton4 valueOf(java.lang.String);
      static {};
    }

    优点:同内部类,且更简洁,更有格调。

    缺点:暂时没想到。


    3.总结

     使用单例模式的时候,应该要考虑是否有必要,如果有必要 的话,还要考虑线程安全问题,一般情况下采用内部类或者枚举的形式实现单例模式就可以了。上文中有一些细节可以进一步查阅相关资料:

    • volatile 关键字,一般用来保证缓存一致性,实际上synchronized同步后也能保证一致性,而volatile还能防止对质量重排序。
    • final关键字,是否从一定程度上提升了查找效率。
    • 应该综合衡量线程安全与效率问题,从而寻找最佳解决方案。
  • 相关阅读:
    虚幻4目录文件结构
    虚幻4编译手记
    几个重要的坐标系
    关于(void**)及其相关的理解
    装饰器总结篇(持续更新ing)
    Linux中find常见用法示例
    linux grep命令
    linux下IPTABLES配置详解
    分布式数据库中间件DDM的实现原理
    消息队列应用场景解析
  • 原文地址:https://www.cnblogs.com/yxlaisj/p/10429888.html
Copyright © 2011-2022 走看看