设计模式之单例模式(创建型)

单例模式的核心,就是全局只有一个实例。下面就每一种创建方式分析其优缺点。

1.饿汉式
/**
 * 饿汉式
 */
public class PersonHungry {
	private static PersonHungry INSTANCE = new PersonHungry();
	private PersonHungry() {}
	public static PersonHungry getInstance() {
		return INSTANCE;
	}
}

饿汉式优点:饿汉式在类加载时,就已经创建了实例对象,故不存在线程安不安全的问题。
饿汉式缺点:类加载时就创建了实例对象,比如只是调用里面某个static方法,这个时候就会创建了该实例对象了,有可能一直都不需要getInstance,因此这样会造成资源浪费。

2.静态代码块
public class Person {
    private static Person INSTANCE = null;
    static {
        INSTANCE = new Person();
    }    
    private Person() {}
    public static Person getInstance() {
        return INSTANCE;
    }
}

静态代码块其实相当于另外一种饿汉式,类加载的时候,对象就会创建,虽说线程安全,但是资源浪费。

3.懒汉式1和2
public class PersonLazy1 {
	private static PersonLazy1 mInstance = null;
	private PersonLazy1() {}
	/**
	 * 该方式在多线程运行下,实例不唯一
	 */
	public static PersonLazy1 getInstance() {
		if (mInstance == null) {
			mInstance = new PersonLazy1();
		}
		return mInstance;
	}
}
public class PersonLazy2 {
	private static PersonLazy2 mInstance = null;
	private PersonLazy2() {}
	/**
	 * 耗性能
	 */
	public synchronized static PersonLazy2 getInstance() {
		if (mInstance == null) {
			mInstance = new PersonLazy2();
		}
		return mInstance;
	}
}

懒汉式1,在多线程的情况下,没法保证只创建一个实例对象。所以演变出懒汉式2,这种方式从效果的角度是可以实现单例的,通过synchronized保证了多线程的安全性。但是,懒汉式2最大的缺点就是耗性能,不管是否已经创建了实例,每次调用getInstance都需要锁。

4.懒汉式3和4
public class PersonLazy3 {
	private static PersonLazy3 mInstance = null;
	private PersonLazy3() {}
	/**
	 * 改进性能,双重判断
	 */
	public static PersonLazy3 getInstance() {
		if (mInstance == null) {
			synchronized (PersonLazy3.class) {
				if (mInstance == null) {
					mInstance = new PersonLazy3();
				}
			}
		}
		return mInstance;
	}
}
/**
 * 推荐使用的方式
 */
public class PersonLazy4 {
	// 加上volatile关键字,最值得推荐的方式
	private static volatile PersonLazy4 mInstance = null;
	private PersonLazy4() {}
	/**
	 * 改进性能,双重判断
	 */
	public static PersonLazy4 getInstance() {
		if (mInstance == null) {
			synchronized (PersonLazy4.class) {
				if (mInstance == null) {
					mInstance = new PersonLazy4();
				}
			}
		}
		return mInstance;
	}
}

懒汉式3和4都是使用双重判断再加锁的形式,即DCL。相比于懒汉式2,改进了性能,只有在第一次getInstance时会比较耗性能。而4比3多了一个关键字叫volatile。那么volatile关键字有什么作用呢?
1、防止重排序。**什么是重排序呢?比如Apple a = new Apple();,创建Apple对象的步骤如下:
(1)在堆中划出一片内存区域,用于存放Apple对象
(2)Apple对象初始化
(3)把堆中的地址赋值给栈中的a
但是,java多线程中,步骤(2)和(3)是有可能调换顺序的。假如先把地址赋值给了a,但是Apple对象还没有初始化。这个时候去判断null就已经不是空了,然后,就把这个对象返回去用,由于其还没有初始化,就有可能各种脏数据,出问题了。
2、线程可见性。**什么是可见性呢?某一个线程改变了公用对象或者变量,在短时间内,另外一个线程有可能是不可见的,因为每个线程都有自己的缓存区域。
综上所述,懒汉式4,即DCL + volatile的方式,是最比较值得推荐的实现方式。

5.静态内部类(推荐使用的方式)
public class PersonStatic {
	private PersonStatic() {}
	public static PersonStatic getInstance() {
		return PersonStaticHolder.INSTANCE;
	}
	/**
	 * 静态内部类
	 */
	private static class PersonStaticHolder {
		private static PersonStatic INSTANCE = new PersonStatic();
	}
}

其实现原理和饿汉式差不多,解决了饿汉式加载类就加载的问题,因为外部类PersonStatic加载时,其内部类PersonStaticHolder是不会随着加载的。当第一次执行getInstance时,执行到PersonStaticHolder.Instance时,PersonStaticHolder类才开始被加载。其实不管多少个线程去调用getInstance方法,返回的都是第一次调用时创建的对象。这种方法不仅能保证了线程的安全性,也保证单例的唯一性,同时也延迟了单例的实例化。
缺点:假如PersonStatic创建时需要传入一个参数,比如Context,那么这种方式实现的单例是不支持。

6.枚举

1、定义一个需要单例的Person类
2、定义枚举类,用于保存Person类对象

public enum PersonEnum {
	INSTANCE;
	private Person mPerson;
	PersonEnum() {
		mPerson = new Person();
		System.out.println("PersonEnum run");
	}
	public Person getPerson() {
		return mPerson;
	}
}

3、调用

public class Test {
	public static void main(String[] args) {
		Person p1 = PersonEnum.INSTANCE.getPerson();
		Person p2 = PersonEnum.INSTANCE.getPerson();
		System.out.println(p1 == p2); // true
	}
}

首先需要明确两点,第一,枚举类的构造方法本身就只支持私有的,即外部是不能直接去new的。第二,JAVA里枚举类的创建本身就是在线程安全的情况下。基于以上两点,第一次调用PersonEnum.Instance实际上就会调用枚举类其自己构造方法,类似于我们创建对象一样,当执行构造方法后,就会创建一个Person对象存储在mPerson变量中。当第二次再调用PersonEnum.Instance时,Instance属性因为已经存在了,所以不会再执行枚举里的构造方法了,从而保证了单例的唯一性。
缺点:Android里并不推荐使用枚举类,有两个原因,第一,较多的枚举类会增加DEX文件大小。第二,枚举比普通常量占用内存大很多。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值