一、介绍
定义:
- 单例模式最初的定义出现于《设计模式》:“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”
- Java中单例模式定义;“一个类有且仅有一个实例,并且自行实例化向整个系统提供该实例。”
优点:
- 提供了对唯一实例的受控访问。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
致谢:
- docdev:http://www.docdev.cn/
- hong15007046964:http://blog.csdn.net/hong15007046964/article/details/51907891
二、Java中的常见写法
懒汉式单例
代码:
1
2
3
4
5
6
7
8
9
10
11
12//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//静态工厂方法
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}介绍:
- 上面的懒汉式单例类实现里对静态工厂方法使用了同步化,必须加锁 synchronized 才能保证单例,但加锁会影响效率。
- 懒汉式其实是一种比较形象的称谓。既然懒,那么在创建对象实例的时候就不着急。会一直等到马上要使用对象实例 的时候才会被创建,懒人嘛,总是推脱不开的时候才会真正执行工作,因此在装载对象的时候不创建对象实例。
- 懒汉式是典型的时间换空间,就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。
饿汉式单例
代码:
1
2
3
4
5
6
7
8
9
10
11//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton {
//私有的默认构造
private Singleton() {}
//已经自行实例化
private static final Singleton single = new Singleton();
//静态工厂方法
public static Singleton getInstance() {
return single;
}
}介绍:
- 饿汉式是典型的空间换时间,当类装载时就会创建类的实例。
- 没有加锁,执行效率会提高。
- 类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载。
双重检查加锁
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Singleton {
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(instance == null){
//同步块,线程安全的创建实例
synchronized (Singleton.class) {
//再次检查实例是否存在,如果不存在才真正的创建实例
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}介绍:
- 双重检查加锁并不是每次进入getInstance()都需要同步,而是先不同步,进入方法后,先检查单例对象是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例(单例对象),这是第二重检查。这样就只需要同步一次,从而减轻了多次在同步情况下进行判断所浪费的时间。
- “双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
- 这种实现方式既可以实现线程安全地创建实例,而又不会对性能造成太大的影响。它只是第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。
- 由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。也就是说,虽然可以使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。
- volatile介绍:http://www.cnblogs.com/yakun/p/3589437.html
静态内部类(推荐)
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class Singleton {
private Singleton(){}
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
* 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。
*/
private static class SingletonHolder{
/**
* 静态初始化器,由JVM来保证线程安全
*/
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}介绍:
当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。
示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65public class RetrofitClient<T> {
public Retrofit mRetrofit;
public ZyErpApi gService;
private RetrofitClient() {
mRetrofit = createRetrofit();
gService = createService(ZyErpApi.class);
}
private static class RetrofitClientHolder {
private static RetrofitClient instance = new RetrofitClient();
}
public static RetrofitClient getInstance() {
return RetrofitClientHolder.instance;
}
/**
* 生成接口实现类的实例
*/
public <T> T createService(Class<T> serviceClass) {
return mRetrofit.create(serviceClass);
}
private Retrofit createRetrofit() {
return new Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
// 设置OkHttpclient
.client(initOkhttpClient())
// RxJava2
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
// 字符串
.addConverterFactory(ScalarsConverterFactory.create())
// Gson
.addConverterFactory(GsonConverterFactory.create())
.build();
}
/**
* 每次请求都会走拦截器
* <p>
* 只需要修改Constants.TOKEN就可以
*/
private OkHttpClient initOkhttpClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (Constants.LOG_TYPE) {
// OkHttp日志拦截器
builder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
}
builder.addInterceptor(new Interceptor() {
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
// 设置请求头,从Debug中看到修改Constants.TOKEN请求header头也会修改
.header("Authorization", Constants.TOKEN)
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
});
return builder.build();
}
}
枚举
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14public enum Singleton {
/**
* 定义一个枚举的元素,它就代表了Singleton的一个实例。
*/
uniqueInstance;
/**
* 单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
}
}介绍:
按照《高效Java 第二版》中的说法:单元素的枚举类型已经成为实现Singleton的最佳方法。用枚举来实现单例非常简单,只需要编写一个包含单个元素的枚举类型即可。
三、Android中典型的单例模式Application类
Application是什么?
- Application和Activity,Service一样,是android框架的一个系统组件,当android程序启动时系统会创建一个 application对象,用来存储系统的一些信息。通常我们是不需要指定一个Application的,这时系统会自动帮我们创建,如果需要创建自己 的Application,也很简单创建一个类继承 Application并在manifest的application标签中进行注册(只需要给Application标签增加个name属性把自己的 Application的名字定入即可)。
- android系统会为每个程序运行时创建一个Application类的对象且仅创建一个,所以Application可以说是单例 (singleton)模式的一个类.且application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。因为它是全局 的单例的,所以在不同的Activity,Service中获得的对象都是同一个对象。所以通过Application来进行一些,数据传递,数据共享 等,数据缓存等操作。
巧妙运用单例模式特点,通过Application来传递数据
- 假如有一个Activity A, 跳转到 Activity B ,并需要推荐一些数据,通常的作法是Intent.putExtra() 让Intent携带,或者有一个Bundle把信息加入Bundle让Intent推荐Bundle对象,实现传递。但这样作有一个问题在 于,Intent和Bundle所能携带的数据类型都是一些基本的数据类型,如果想实现复杂的数据传递就比较麻烦了,通常需要实现 Serializable或者Parcellable接口。这其实是Android的一种IPC数据传递的方法。如果我们的两个Activity在同一个 进程当中为什么还要这么麻烦呢,只要把需要传递的对象的引用传递过去就可以了。
- 基本思路是这样的。在Application中创建一个HashMap ,以字符串为索引,Object为value这样我们的HashMap就可以存储任何类型的对象了。在Activity A中把需要传递的对象放入这个HashMap,然后通过Intent或者其它途经再把这索引的字符串传递给Activity B ,Activity B 就可以根据这个字符串在HashMap中取出这个对象了。只要再向下转个型 ,就实现了对象的传递。