设计模式--01.单例模式

付威     2019-10-27   2376   6min  

在程序设计中,需要保证一个只有一个对象实例,就是所谓的单例模式。在java中,有很多单例模式的实现,这篇博客是对这几种单例模式的优缺点进行分析和优化:

饿汉模式


所谓饿汗模式,就是优先创建对象,对象在类加载的时候就已经创建好了,具体的代码如下:

 
	private static Singleton01 instance = new Singleton01();

	public static Singleton01 getInstance() {
		return instance;
	}

饿汗模式优点是**代码简单,由于是在类加载的时候就已经创建好了对象,所以不存在线程安全的问题。 **

缺点是:有的类在没有使用的时候就已经创建了对象,产生了多余的垃圾对象。


懒汉模式


懒汉模式是针对饿汉模式的一个优化,类只有在使用的时候才会进行实例化:

    private static Singleton02 instance;
	//懒汉模式
	//简单,但是线程不安全
	public static Singleton02 getInstance() {
		if (instance == null) {
			instance = new Singleton02();
		}
		return instance;
	}

懒汉模式的实例在使用的时候才去创建,但是创建的过程是线程不安全的,在多线程的环境下存在多个实例的现象。


double check模式


doublie check模式优化了懒汉模式的线程安全的问题,使用锁来保证当前只有一个线程创建实例。

   private static Singleton03 singleton03;
	
	//加锁,线程安全,但是效率慢
	public synchronized static Singleton03 getInstance() {
		if (singleton03 == null) {
			singleton03 = new Singleton03();
		}
		return singleton03;
	}

上面的代码足够保证了线程的安全,但是多线程的场景下会存在一个线程工作,其他线程盲等的现象,所以再为了优化这个问题,再创建进入锁之前再次进行检查, 使用volatile关键字保证变量在多线程中是共享的,代码如下:

private volatile static Singleton05 singleton05;
	//double check 性能最好
	public static Singleton05 getInstance() {
		if (singleton05 == null) {
			synchronized (Singleton05.class) {
				if (singleton05 == null) {
					singleton05 = new Singleton05();
				}
			}
		}
		return singleton05;
	}

虽然doublecheck的模式,继承了懒汉模式的优点,也不会有线程安全的问题,但还是会存在线程盲等的现象,优化只能降低发生的概率,无法完全避免。


内部类模式


内部类模式是一个比较精妙的设计,是利用了Jvm的类的加载的特点,具体代码如下:


	public static Singleton06 getInstance() {
		return Inner.INSTANCE;
	}
	
	static class Inner {
		final  static Singleton06 INSTANCE = new Singleton06();
	}
 

只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式。
缺点是会产生多余的类,反射也可以破解,通过构造函数可以防止破解


枚举单例模式


枚举是《effective java》中推荐的单例模式,利用jvm的枚举类无法被反射的创建实例的特点,所以枚举类相对更加安全和健壮。具体的代码如下:

    public enum Singleton07 {
        INSTANCE;
        Singleton07() {
            System.out.println(this.getClass().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

虽然枚举模式有点很多,但是再实际开发中却用的很少,原因是枚举类的用法比较单一(不支持继承)。


使用memoize创建单例


使用Guavamemoize函数的特性, 只再第一此获取的时候才调用,可以用来实现单例模式,代码如下:

private static final Supplier<Singleton08> InstanceSuppler = Suppliers.memoize(Singleton08::new);
	
	public static Singleton08 getInstance() {
		return InstanceSuppler.get();
	}

使用memoize函数,可以支持多种的实例函数的调用方式,也可以制定方法的过期的时间,更为灵活,个人推荐使用memoize方式

(本文完)

作者:付威

博客地址:http://blog.laofu.online

如果觉得对您有帮助,可以下方的RSS订阅,谢谢合作

如有任何知识产权、版权问题或理论错误,还请指正。

本文是付威的网络博客原创,自由转载-非商用-非衍生-保持署名,请遵循:创意共享3.0许可证

交流请加群113249828: 点击加群   或发我邮件 laofu_online@163.com

付威

获得最新的博主文章,请关注上方公众号