android单例模式:一个类只有一个实例对象。
这种情况还是比较普遍的,比如我家客厅的电视,就一台,我和我媳妇两个人看,虽然遥控器只有一个,但现在手机都可以模拟遥控器了。这种情况下,电视在编程里就只能是单例模式。那么单例模式代码怎么实现呢?
首先想到的写法:
//静态实例instance是私有,但也是唯一的。
//构造方法为私有的,这样外部就无法直接new创建对象了
//提供public static方法getInstance()用于获取实例
public class TV{
private static TV instance;
private static TV(){};
public static TV getInstance(){
if(instance==null){
instance=new TV();
}
return instance;
}
}
看起来不错,有没有问题呢?有的,那就是如果多个线程都调用的话,会有冲突。比如线程A判断instance为空,正在创建对象;线程B去判断,发现instance也为空,就也会去创建对象。等到线程A创建完成去使用之后,线程B又new出新对象,线程A的实例就指向线程B新建的实例了,这就乱套了。
所以要进行线程同步,使用synchronized关键字。
public class TV{
//私有的静态变量
private static TV instance;
//私有的构造方法
private TV(){};
//公有的同步静态方法
public static synchronized TV getInstance() {
if (instance == null) {
instance = new TV();
}
return instance;
}
}
通过为getInstance()方法加synchronized关键字,使得这个方法变为线程安全的方法,同一时刻只能由一个线程访问。
那么有没有改进的空间呢呢?还是有的,getInstance()方法无论instance是否为空,都会维持一个线程访问,其实没必要,如果instance已经不为空了,多个线程完全可以同时访问。
改进synchronized的位置
public class TV{
private static TV instance;
private TV (){}
public static TV getInstance() {
if (instance== null) {
//同步位置
synchronized (TV.class) {
if (instance== null) {
instance= new TV();
}
}
}
return instance;
}
}
这样我们会首先判断instance是否为空,如果为空再执行同步代码去创建新的对象。但是里面又再次判断instance是否为空,这是为什么呢?其实因为第一次判断是否为空到执行synchronized代码这部分是线程不安全的。
那么这样写有没有风险呢?
instance = new TV()并不是一个原子操作,这句代码最终会被编译成多条汇编指令,做如下事情:
- 为TV实例分配内存
- 调用TV的构造函数
- 将instance指向分配的内存
为什么我没有写1,2,3做序号呢?是由于Java编译器允许处理器乱序执行。在jdk1.5之前,JMM(Java Memory Model:java内存模型)中Cache、寄存器、到主内存的回写顺序不保证执行顺序。而一旦将instance指向分配的内存,instance就不为空了,这时instance可能还不能真正使用。
在JDK1.5之后,我们可以使用官方提供的volatile关键字:
private volatile static TV instance; //使用volatile关键字
那么volatile关键字起什么作用呢?
变量放在主存区上,使用该变量的每个线程,都将从主存区拷贝一份到自己的工作区上进行操作。
volatile,声明这个字段易变(可能被多个线程使用),Java内存模型负责各个线程的工作区与主存区的该字段的值保持同步,即一致性。
而static声明这个字段是静态的(可能被多个实例共享),在主存区上该类的所有实例的该字段为同一个变量,即唯一性。
volatile, 声明变量值的一致性;static,声明变量的唯一性。
仅用static的话,无法保证多线程的一致性,因此需要volatile.
可不可以不使用synchronized呢?也是可以的;
public class TV{
//static修饰的静态变量在内存中一旦创建,便永久存在
private static TV instance = new TV();
private TV(){}
public static TV getInstance() {
return instance;
}
}
是不是感觉比使用synchronized代码简单多了,这种写法的唯一问题是静态变量在类加载的时候就在内存里创建了,而这时候可能根本不会有使用实例的需求。
解决它
public class TV{
private TV(){} ;//私有的构造函数
public static final TV getInstance() {
return TVHolder.INSTANCE;
}
//定义的静态内部类
private static class TVHolder {
private static final TV INSTANCE = new TV(); //创建实例的地方
}
}
这样,第一次加载TV类的时候,instance并未初始化。而等到getInstance()方法调用的时候才初始化,延迟初始化动作。
除此之外,还有一种枚举单例
class TV{
}
public enum Anything{
INSTANCE;
private TV instance;
Anything() {
instance = new TV();
}
public TV getInstance() {
return instance;
}
}
//使用时
TV tv=Anyting.INSTANCE.getInstance();
是不是发现很简单?没用到static,没用到synchronized。其实它是借助了Enum类的特性,保证了实例在任何情况下只有一个(包括反序列化后)。但是枚举会占用更多内存,感兴趣的可以自己去查询。
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable
反序列化会破坏单例吗?会的
单例与序列化的那些事儿
通过这个链接,就看以了解到。其实就是反序列化时,会通过反射类的构造方法产生新的实例,那自然单例模式就被破坏了。因此,我们枚举单例前的所有方法都要为此增加一个方法保证反序列化后单例模式的有效性:
private Object readResolve(){
return instance;
}
还有其他的方式吗? 有的,保存实例。
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String,Object>();//使用HashMap作为缓存容器
private Singleton() {
}
public static void registerService(String key, Objectinstance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;//第一次是存入Map
}
}
public static ObjectgetService(String key) {
return objMap.get(key) ;//返回与key相对应的对象
}
}
这种方法就是第一次产生实例,我们就保存它,以后取出来就用。Android中的系统核心服务以单例形式存在,减少了资源消耗。
总结:
不管以哪种形式实现单例模式,目标都是为了保证实例的唯一性,有效性,线程安全,还要考虑反序列化的情况。只要在使用时考虑到这几个方面,单例模式的实现并不复杂。