单例模式

一. 概念简述:

在软件开发中我们最常用的一个设计模式也许就是单例模式了,单例模式也是所有设计模式中最简单的,单例模式指的是一个类有且只能创建一个实例对象。

二. 实现思路:

1. 定义私有静态变量mInstance,mInstance类型为当前类的对象,用来持有单例唯一的实例;

2. 将构造方法定义为private,使外界不能随意调用来创建该类的对象;

3. 定义一个静态的公共方法getInstance(),在此方法中调用该类私有的构造方法。

三. 具体实现:

从实现思路看以看出单例模式的具体实现方法不止一种.

1. 饿汉式
     饿汉式是该类单例的实例在该类加载的时候就创建,饿汉式代码如下:

public class SingleInstance{
    private static SingleInstance mInstance = new SingleInstance();
    private SingleInstance(){}
    public static SingleInstance getInstance(){
        return mInstance;  
    }  
}

 饿汉式优缺点:

   1. 如果该类的构造方法中有较多的操作处理,这时饿汉式将会导致该类的加载比较慢,往往影响程序的性能;

   2. 如果该类进行了加载,在短时间内并没有使用的话则会占用内存,造成资源的浪费;

   3. 这种方式也有一个好处,因为单例对象是在本类加载的时候去创建的,因而是线程安全的。


2.懒汉式

懒汉式指的是该类的对象在第一次真正使用的时候去创建,直接上代码,

public class SingleInstance{
    private static SingleInstance mInstance;
    private SingleInstance(){}
    pulic static SingleInstance getInstance(){
        if(mInstance == null){
            mInstance = new SingleInstance();  
        }
        return mInstance;
    }
}

 懒汉式优缺点:

   1. 从上述代码可以看出,懒汉式成功避免了饿汉式的缺点,可以做到延迟创建单例对象,提高内存利用率;

   2. 要知道任何事是是没有绝对的好和绝对的坏的,懒汉式也存在一个致命的缺点,那就是线程不安全,如果有两个线程同时调用getInstance()方法时就有可能创建两个该类的对象,这不是我们想看到的;

上面这两种方式都存在一个问题,那就是 线程不安全.

为什么说这种写法是不是线程安全呢?
假设 SingleInstance 类刚刚被初始化,mInstance 对象还是空,这时候两个线程同时访问 getInstance 方法: 
因为 mInstance 是空,所以两个线程同时通过了条件判断,开始执行new操作:这样一来,显然 mInstance 被构建了两次;

线程安全的写法:

写法1:

public class SingleInstance{
    private static SingleInstance mInstance;
    private SingleInstance(){}
    pulic static synchronized SingleInstance getInstance(){
        if(mInstance == null){
            mInstance = new SingleInstance();  
        }
        return mInstance;
    }
}

加上synchronized关键字之后,getInstance方法就会锁上了。如果有两个线程Thread1、Thread2同时执行到这个getInstance方法时,会有其中一个线程Thread1获得同步锁,得以继续执行,而另一个线程Thread2则需要等待,当第Thread1执行getInstance方法完成了null判断、对象创建、获得返回值之后,Thread2线程才会执行执行, 所以这端代码是线程安全的, 避免了可能出现因为多线程导致多个实例的情况。但是这种写法也有一个问题:给gitInstance方法加锁,虽然会避免了可能会出现的多个实例问题,但是会强制除Thread1之外的所有线程等待,实际上会对程序的执行效率造成负面影响。

好, 我们再次改进代码, 使用双重加锁机制来优化线程安全的单例模式:

写法2:   双重加锁机制

public class SingleInstance{
    private static SingleInstance mInstance;
    private SingleInstance(){}
    pulic static SingleInstance getInstance(){
        if(mInstance == null){
            synchronized (SingleInstance.class) {
                if(mInstance == null){
                    mInstance = new SingleInstance();  
                }
            }
        }
        return mInstance;
    }
}
第一个if (mInstance == null),其实是为了解决写法1中的因为在方法外面加锁导致的效率问题,只有mInstance为null的时候,才进入synchronized的代码段——大大减少了几率。

第二个if (mInstance == null),则是跟 写法2 一样,是为了防止可能出现多个实例的情况;

写法3   volatile 防止指令重排导致

public class SingleInstance{
    private static volatile SingleInstance mInstance;
    private SingleInstance(){}
    pulic static SingleInstance getInstance(){
        if(mInstance == null){
            synchronized (SingleInstance.class) {
                if(mInstance == null){
                    mInstance = new SingleInstance();  
                }
            }
        }
        return mInstance;
    }
}
在成员变量 mInstance 前面加了volatile关键字,Java内存模型,为了执行代码的执行效率,会对处理器的指令重新排序. 即代码的执行顺序可能会被打乱;
在执行
mInstance = new SingleInstance(); 
这条命令语句时,Java内存模型并不是一次性就执行完毕的,即该操作不具有是原子性,执行这句代码分为三步:
1. 为对象分配内存
2. 执行构造方法语句,初始化实例对象
3. 把mInstance的引用指向分配的内存空间

在Java 内存模型中这三个步中的2和3不一定是顺序执行的,假定如果线程A执行的顺序为1、3、2,在第2步执行完毕的时候,恰好线程B执行第一次判空语句,则会直接返回mInstance,那么此时获取到的mInstance仅仅只是不为null,实质上没有初始化,这样最终返回的单例对象就是有问题的;



写法4   静态内部类线程安全

public class SingleInstance{

    private SingleInstance(){}

    pulic static SingleInstance getInstance(){
        return SingleHolder.mInstance;
    }
    
    public static class SingleHolder{
        private static SingleInstance mInstance = new SingleInstance();
    }
}
当外部类 SingleInstace 被加载时,其静态内部类SingleHolder不会被加载,所以它的成员变量mInstance是不会被初始化的,只有当调用SingleInstace.getInstance()方法时,才会加载SingleHolder并且初始化其成员变量,而类加载时是线程安全的,这样既保证了延迟加载,也保证了线程安全,同时也简化了代码量.


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值