关于单例模式

五种实现方式

1.饿汉式

1
2
3
4
5
6
7
8
9
10
public class Singleton{

private static Singleton instance = new Singleton();

private Singleton() {};

public static Singleton getInstance(){
return instance;
}
}

类加载的时候就会创建对象,只会创建一次,在多线程情况下仍然能保持唯一的实例。如果单例类占用比较大的内存,创建饿汉式就不是一个太好的选择

2.懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton{

private static Singleton instance;

private Singleton() {};

public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}

}

需要的时候才实例化对象,实现了延缓加载,但是,在多线程中无法保证示例的唯一性。

加锁的懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton{

private static Singleton instance;

private Singleton() {};

public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}

}

3. DCL(Double Check Lock , 双重校验锁)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton{

private static volatile Singleton instance;

private Singleton() {};

public static Singleton getInstance(){
if(instance == null){ // 如果已经创建,就不会获取对象锁,提高性能
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}

4.静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton{

private Singleton() {};

private static class SingletonHolder{
public static Singleton instance = new Singleton();
}

public static Singleton getInstance(){
return SingletonHolder.instance;
}
}

利用JVM类加载机制,线程安全

5.枚举

1
2
3
4
5
6
7
public enum Singleton{
INSTANCE;
}

// 使用

Singleton singleton = Singleton.INSTANCE;

所有枚举常量都通过静态代码块来进行初始化,即在类加载期间就初始化,这样保证了每个枚举类型及枚举常量都是不可变的,所以枚举类型的单例是线程安全的。

synchronized

synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。它包括两种用法:synchronized 方法和 synchronized 块。

Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍可以访问该object中的非加锁代码块。

volatile

当一个变量被volatile修饰后,将具备两种特性:

  • 可见性:保证此变量对所用线程的可见性

  • 禁止指令重排序优化

单例模式的序列化和反序列化

对单例类的实例的序列化会破会单例模式的唯一性,反序列化会得到一个不同的实例。
解决办法:在单例类中定会readResolve方法,以DCL为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Singleton{

private static volatile Singleton instance;

private Singleton() {};

public static Singleton getInstance(){
if(instance == null){ // 如果已经创建,就不会获取对象锁,提高性能
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}

private Object readResolve(){
return instance;
}
}

单例模式与反射

Java的反射机制可以破坏单例模式

1
2
3
4
5
6
7
8

Class<Singleton> classType = Singleton.class;
Constructor<Singleton> constructor = classType.getDeclaredConstructor(null);
constructor.setAccessible(true);
Singleton singleton1 = constrctor.newInstance();
Singleton singleton2 = Singleton.getInstance();

singleton1.equals(singleton2) // 结果是false

解决措施,以DCL为例

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
public class Singleton{

private static volatile Singleton instance;
private static boolean flag = false;

private Singleton() {
synchronized(Singleton.class){
if(!flag){
flag = true;
}else{
throw new RuntimeException("单例对象被重复创建");
}
}
}

public static Singleton getInstance(){
if(instance == null){ // 如果已经创建,就不会获取对象锁,提高性能
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}

常见单例模式应用

1.EventBus.getDefault()

1
2
3
4
5
6
7
8
9
10
11
12

/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}

2. Picasso.get()

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
/**
* The global {@link Picasso} instance.
* <p>
* This instance is automatically initialized with defaults that are suitable to most
* implementations.
* <ul>
* <li>LRU memory cache of 15% the available application RAM</li>
* <li>Disk cache of 2% storage space up to 50MB but no less than 5MB. (Note: this is only
* available on API 14+ <em>or</em> if you are using a standalone library that provides a disk
* cache on all API levels like OkHttp)</li>
* <li>Three download threads for disk and network access.</li>
* </ul>
* <p>
* If these settings do not meet the requirements of your application you can construct your own
* with full control over the configuration by using {@link Picasso.Builder} to create a
* {@link Picasso} instance. You can either use this directly or by setting it as the global
* instance with {@link #setSingletonInstance}.
*/
public static Picasso get() {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
if (PicassoProvider.context == null) {
throw new IllegalStateException("context == null");
}
singleton = new Builder(PicassoProvider.context).build();
}
}
}
return singleton;
}

3. InputMethodManager.getInstance()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public final class InputMethodManager {
//内部全局唯一实例
static InputMethodManager sInstance;

//对外api
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
sInstance = new InputMethodManager(service, Looper.getMainLooper());
}
return sInstance;
}
}
}

参考:Android设计模式之单例模式

坚持原创技术分享,您的支持将鼓励我继续创作!