在软件开发中我们最常用的一个设计模式也许就是单例模式了,单例模式也是所有设计模式中最简单的,单例模式指的是一个类有且只能创建一个实例对象。
二. 实现思路:
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()方法时就有可能创建两个该类的对象,这不是我们想看到的;
上面这两种方式都存在一个问题,那就是 线程不安全.
线程安全的写法:
写法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 = new SingleInstance();
这条命令语句时,Java内存模型并不是一次性就执行完毕的,即该操作不具有是原子性,执行这句代码分为三步:
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();
}
}