常用的设计模式-单例模式

常见的单例模式:饿汉式、懒汉式、双重校验(加强懒汉式)、静态内部类、枚举

饿汉式:

public class Singleton {
	private static Singleton singleton=new Singleton();
	private Singleton(){	
	}
	public static Singleton newInstance(){
		return singleton;
	}
}

优点:不存在线程安全问题。

缺点:类加载就创建了单例,如果Singleton对象比较大的话,这种创建方式不推荐。

懒汉式:

public class Singleton {
	private static Singleton singleton=null;
	private Singleton(){
	}
	public static Singleton newInstance(){
		if(singleton==null){
			singleton=new Singleton();
		}
		return singleton;
	}
}

优点:弥补了饿汉式类加载就创建单例的缺点;

缺点:单线程使用是没有问题的,如果是多线程会出现线程安全问题。

双重校验(加强懒汉式)

public class Singleton {
	private volatile static Singleton singleton=null;
	private Singleton(){
		
	}
	public static Singleton getInstance(){
		if(singleton==null){//#1
			synchronized (Singleton.class) {//#2
				if(singleton==null){//#3
					singleton=new Singleton();//#4
					System.out.println("创建了!!!!!!!!!!!!!!!!!!!");//#5
				}//#6
			}//#7
		}
		return singleton;
	}
	
} 

优点:弥补了懒汉式的线程安全问题;

缺点:创建单例时需要两次判断,在代码复杂的情况下容易出错。

以下几点需要注意:

  1. 构造方法定义为private,防止其他类通过构造方法创建对象,破坏单例模式。
  2. synchronized(Singleton.class)很重要,不是synchronized(this)。两个的区别,前者是同步当前类,后者是同步一个对象
  3. volatile关键字,这个很重要,如果不加上这个关键字,很可能出现问题,虽然概率极低。具体来说就是synchronized虽然保证了原子性,但却没有保证该区域指令重排序的正确性,会出现A线程执行初始化,但可能因为构造函数里面的操作太多了,所以A线程的uniqueInstance实例还没有造出来,但已经被赋值了。而B线程这时过来了,错以为uniqueInstance已经被实例化出来,一用才发现uniqueInstance尚未被初始化。要知道我们的线程虽然可以保证原子性,但程序可能是在多核CPU上执行。。
    就是说可以通过volatile关键字来保证对象singleton=new Singleton()这段代码的有序性,先创建对象再给引用赋值,而不是先给引用赋值,再创建对象。参见【深入理解jvm高级特性】12章,java内存模型与线程
  4. jdk1.5以上使用,1.5以下volatile对重排序不起作用。

静态内部类:

public class Singleton {
	
	private Singleton(){
	}
	private static class SingletonHolder{
		private static Singleton singleton=new Singleton();
	}
	public static Singleton newInstance(){
		return SingletonHolder.singleton;
	}
}

优点:在内部类加载时创建单例,而不是在singleton类加载时创建,弥补了饿汉式的缺点。同时线程是安全的。推荐使用。

枚举:

public enum Singleton {
    INSTANCE;
    private Object object;
    Singleton() {
        object = new Object();
    }
    public Object getInstance(){
        return object;
    }
}

前边四种都有共同的缺点:

1)需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象时都会创建一个新的实例。

2)可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。

而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,《Effective Java》作者推荐使用的方法。不过,在实际工作中,很少看见有人这么写。

单例模式在安卓中的应用(摘抄:https://www.cnblogs.com/android-blogs/p/5530239.html)

比如Android-Universal-Image-Loader中的单例

1
2
3
4
5
6
7
8
9
10
11
12
private volatile static ImageLoader instance;
/** Returns singleton class instance */
public static ImageLoader getInstance() {
	if (instance == null) {
		synchronized (ImageLoader.class) {
			if (instance == null) {
				instance = new ImageLoader();
			}
		}
	}
	return instance;
}

比如EventBus中的单例

1
2
3
4
5
6
7
8
9
10
11
private static volatile EventBus defaultInstance;
public static EventBus getDefault() {
	if (defaultInstance == null) {
		synchronized (EventBus.class) {
			if (defaultInstance == null) {
				defaultInstance = new EventBus();
			}
		}
	}
	return defaultInstance;
}

上面的单例都是比较规规矩矩的,当然实际上有很多单例都是变了一个样子,单本质还是单例。

如InputMethodManager 中的单例

1
2
3
4
5
6
7
8
9
10
11
static InputMethodManager sInstance;
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;
	}
}

AccessibilityManager 中的单例,看代码这么长,其实就是进行了一些判断,还是一个单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static AccessibilityManager sInstance;
public static AccessibilityManager getInstance(Context context) {
	synchronized (sInstanceSync) {
		if (sInstance == null) {
			final int userId;
			if (Binder.getCallingUid() == Process.SYSTEM_UID
					|| context.checkCallingOrSelfPermission(
							Manifest.permission.INTERACT_ACROSS_USERS)
									== PackageManager.PERMISSION_GRANTED
					|| context.checkCallingOrSelfPermission(
							Manifest.permission.INTERACT_ACROSS_USERS_FULL)
									== PackageManager.PERMISSION_GRANTED) {
				userId = UserHandle.USER_CURRENT;
			} else {
				userId = UserHandle.myUserId();
			}
			IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
			IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder);
			sInstance = new AccessibilityManager(context, service, userId);
		}
	}

枚举:枚举被评为 实现单例的最佳方法,但网上很少见到有这么用。而且枚举可以防止反射漏洞。


 
public enum Singleton {
    INSTANCE;
    private Object object;
    //构造方法
    Singleton() {
    	//想要创建的单例
        object = new Object();
    }
    public Object getInstance(){
        return object;
    }
}

综合来说,双重校验(DCL)和静态内部类模式使用的比较多。枚举最佳。可以尝试使用。

参考:https://www.2cto.com/kf/201606/517959.html

          https://blog.youkuaiyun.com/jm_heiyeqishi/article/details/51052889

          https://blog.youkuaiyun.com/goodlixueyong/article/details/51935526

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值