作为所有设计模式中最简单的一种。可以说是只有一个对象,而这个对象是独立无二的。
确保一个类只有一个实例,并提供一个全局访问点
在开发中经常要new 对象,但是面对重复的对象,大量创建的话,会给项目添加大量的重复代码。所以就需要用到单例模式。
单例模式的应用场景有很多比如线程池(threadpoon)、缓存(cache)、对话框等。
拿缓存来举例:
在app登录的时候需要用户名和密码,但是一般我们不直接保存用户名和密码,这样显然是不安全的。
这里用到了一个token,来作为全局请求的值。下次登录的时候我们只需要去提交token值就行了。
而如何储存这一唯一的对象?这里就用到了单例模式。
在Android中有五大存储方式,
1、SharedPrederences
2、ConetentProvider
3、I/O存储
4、SQLiteDatabase
5、网络存储
这里用到了五大存储方式之一的SharedPrederences,该方式用于存储一些基本的配置,比如token。全局只有一个。
一、getInstance
所以就需要创建一个全局的操作类,
public class SharedPrefHelper {
private static SharedPrefHelper sharedPrefHelper = null;
private static SharedPreferences sharedPreferences;
public static SharedPrefHelper getInstance(){
if (null == sharedPrefHelper) {
sharedPrefHelper = new SharedPrefHelper();
}
return sharedPrefHelper;
}
这样就是一个简单的单例类,在SharedPrefHelper只有一个getInstance静态方法。
在通过getInstance()初始化的时候,会先去判断实例(sharedPrefHelper)是否为空。
如果不为空的话就会直接给我们返回实例。
这样就能保证对象只被实例化一次。
到这里已经可以把单例模式搬到项目中进行使用了。
但是......
如果在一个稍微大点的实际项目中,可能不是单单只有一个线程。如果出现了两个线程或者多个线程同时进行实例化的调用,可能会造成的结果是:被实例化了两次,出现两个对象的问题。
二、synchronized
为了解决线程冲突的问题,我们需要引入锁的机制。
将synchronized添加到方法体:
public static synchronized SharedPrefHelper getInstance(){
if (null == sharedPrefHelper) {
sharedPrefHelper = new SharedPrefHelper();
}
return sharedPrefHelper;
}
synchronized:
当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
虽然能解决线程问题,但是会带来性能的下降。拿抢票系统来说,当10000人抢一张票的时候,都去排队,那么就肯定会造成性能问题。
《HeadFirsrt》一书中说道
同步(synchronized) getInstance()的方法既简单又有效,但是你必须知道,同步一个放到可能造成程序执行效率下降100倍。
虽然可能说没有100倍这么夸张,但是当多线程迸发的情况下,是会影响性能,造成线程的阻塞。
或者是考虑使用双重锁
三、Volatile
Volatile是一个变量修饰符,用来通知JVM对于当前的变量的值,每次都要重新读取。
Volatile也可以看作是为了防止加锁线程造成的线程阻塞。
volatile是一个类型修饰符就像大家更熟悉的const一样,它是被设计用来修饰被不同线程访问和修改的变量,volatile的作用是作为指令,确保本条指令不会因编译器,且要求每次直接读值。
volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。
private static volatile SharedPrefHelper sharedPrefHelper = null;
private static SharedPreferences sharedPreferences;
public static SharedPrefHelper getInstance(){
if (null == sharedPrefHelper) {
synchronized (sharedPrefHelper.getClass()){
if (null == sharedPrefHelper){
sharedPrefHelper = new SharedPrefHelper();
}
}
}
return sharedPrefHelper;
}
总结
三种方法各有利弊,在实际开发中要使用需要结合实际情况。
一般常用的是第二种。
上面只是结合Android的基本数据存储来了解单例模式,其他应用的地方还有很多比如一些输入法管理器、蓝牙管理器、日历等。
注意:
1、在Java1.2版本以前单例模式如果没有在注册表中注册,将会被垃圾回收机制回收。
2、在Java1.5版本以前,双重锁机制将失效。
相关参考:
《Head First设计模式》
《Java 并发编程》