对于一个单例来说,应该注意的大概只有下面一点。
确保程序从始至终只能操纵一个对象。
首先需要考虑的是一个单例必须要注意的问题。
1.保证这个类只能被创建一次。
2.整个程序只能访问到一个对象。
基于这几点可以设计出这样的一个类。
Singleton.java
public class Singleton {
// 静态变量保证应用程序只有一个变量
private static Singleton mInstance;
// 私有化构造方法保证对象只能在这个类内部被创建
private Singleton() {
...
}
// 为其它类提供一个可以获得这个变量的公共方法
public static Singleton getInstance() {
return mInstance;
}
}
接下来需要考虑的就是何时去创建这样一个对象,一般来说常用的有两种方法“懒汉式(Lazy initialization)”和“饿汉式(Eager initialization)”。
“懒汉式”就是在这个对象第一次被使用的时候进行创建,而“饿汉式”就是在类进行初始化的时候就创建”,这两种方法的加载分别会导致两种不同的问题。
“懒汉式”改动的代码
public static Singleton getInstance() {
if (mInstance == null) {
mInstance = new Singleton();
}
return mInstance;
}
导致的问题就是,当两个线程同时去获得这个单例时,有可能会获得两个不同的对象,这种做法是线程不安全的,不考虑执行效率问题的情况下,可以添加 synchronized 来保证这个方法同时只有一个线程在访问,更改后的代码如下
public static synchronized Singleton getInstance() {
...
}
“饿汉式”改动的代码
private static final Singleton mInstance = new Singleton();
看起来这种方法简单易懂,也不用考虑线程安全问题,但如果程序中任何以这种方式创建的静态变量过多,或者单例构造方法中有比较耗时的操作,则这个程序的启动时间就会变长,而且这个对象无论有没有被使用都会被创建,算是一种资源上的浪费。然而最大的问题是有些情况下无法使用“饿汉式”,比如单例的构造方法中有参数。
所以在保证线程安全的情况下,“懒汉式”比“饿汉式”节省程序启动时间与对象的所占用的内存。“饿汉式”比“懒汉式”节省第一次使用单例时所消耗的时间。
然而,这两种方式并没有什么大的区别,个人感觉“懒汉式”适用范围更广一些,因为有好多事情可能需要在程序启动的时候要做,单例对象的话,能省点时间是一点,况且用户在使用时感觉偶尔的一次卡顿,也许是系统卡了一下也说不定。(๑•́ ₃ •̀๑)
最后,说明一下上面那种“懒汉式”的执行效率问题,线程同步是很费时间的,所以,单例类最好写成这样
public class Singleton {
private static volatile Singleton mInstance;
private Singleton() {
...
}
public static Singleton getInstance() {
if (mInstance == null) {
synchronized (Singleton.class) {
if (mInstance == null) {
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
这个处理方式的话叫做“双重检查锁(double-checked locking)”,确保了线程安全的前提下又提升了执行效率,然而会发现一个并不认识的关键字 volatile ,这个关键字的作用是防止下面这种情况发生。
线程1 首次访问 getInstance 方法,mInstance 为空, 将 mInstance 对象指向分配的内存空间,同时,线程2 访问,mInstance 不为空,线程2 获得 mInstance 对象的复制, 由于线程锁的问题所以线程2 需要等待线程1 构造方法执行完毕,线程2调用 mInstance 的方法,但由于是复制的时候线程1 还没有把真正的数据写入主内存中,所以线程2 获得的对象实际内存内容为空,然后报错。o(^▽^)o~♪
(以上内容由于是我自己编的,而且很难进行测试,所以看看就好。)
volatile 的作用,就是跳过线程对对象的复制,直接对主内存进行操作(大概),保证线程安全,反正单例的话这样写没错就对了(1.5之前的java应该没人用了吧)。