reupe

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

单例模式是开发程序过程中最常见的开发模式之一,很多优秀的框架都是采用单例模式设计的,比如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关键字,是否从一定程度上提升了查找效率。
  • 应该综合衡量线程安全与效率问题,从而寻找最佳解决方案。
posted on 2019-02-25 17:56  yxlaisj  阅读(145)  评论(0编辑  收藏  举报