/**
* 方式一
* instance 单例初始值是null,还未构建,则构建单例对象并返回;是懒汉模式 - 线程不安全
* instance 单例对象一开始就被new 出来,主动构建,则不需要判空操作;是饿汉模式 - 线程安全
*/
private ZpDanLiDemo(){}
private static ZpDanLiDemo instance = null;
public static ZpDanLiDemo getInstance() {
// (场景1:ZpDanLIDemo刚被初始化,线程1、2两个线程同时调用 getInstance 方法,
// 所以instance为空,两个线程同时通过条件判断,对象创建了两次)
if (instance == null) {
instance = new ZpDanLiDemo();
}
return instance;
}
注:这种方式是线程不安全的,具体原因可以看注释描述。
懒汉与饿汉两种单例模式总是傻傻分不清,应用与面试的时候要多注意。
/**
* 方式二
*/
private ZpDanLiDemo(){}
// volatile 对象new的时候,JVM执行顺序保证正常执行
private volatile static ZpDanLiDemo instance = null;
public static ZpDanLiDemo getInstance() {
// 双重检测机制
if (instance == null) {
// 同步锁 (为了对象不被 new 多次,使用同步锁,锁住整个类)
synchronized (ZpDanLiDemo.class) {
// 双重检测机制 (进入synchronized临界区以后,还要再做一次判空。
// 因为当两个线程同时访问的时候,线程A构建完对象,线程B也已经通过
// 了最初的判空验证,不做第二次判空的话,线程B还是会再次构建instance对象)
if (instance == null) {
instance = new ZpDanLiDemo();
}
}
}
return instance;
}
注:这种方式是线程安全
/**
* 方式三 静态内部类实现单例模式
* 从外部是无法访问静态内部类lazyHolder,只有当调用getInstance方法的时候,才能得到单例对象。
* instance 对象初始化的时机并不是在单例类ZpDanLiDemo被加载的时候,而是在调用
* getInstance方法,使得静态内部类LazyHolder被加载的时候。
* 因此这种实现方式是利用classLoader的加载机制来实现懒加载,并保证构建单例的线程安全。
*/
private ZpDanLiDemo(){}
private static class LazyHolder {
private static final ZpDanLiDemo instance = new ZpDanLiDemo();
}
public static ZpDanLiDemo getInstance() {
return LazyHolder.instance;
}
注:线程安全
使用静态内部类构建单例,事件比较靠谱的一件事儿。个人喜好,是比较喜欢用这种方式。
使用反射机制打破单例
/**
* 利用反射打破单例
* 使用枚举可以防止反射构建
*/
private void getDanLi() {
try {
// 获得构造器
Constructor con = ZpDanLiDemo.class.getDeclaredConstructor(ZpDanLiDemo.class);
// 设置为可访问
con.setAccessible(true);
// 构造两个不同的对象
ZpDanLiDemo zpDanLiDemo1 = new ZpDanLiDemo();
ZpDanLiDemo zpDanLiDemo2 = new ZpDanLiDemo();
// 验证是否是不同对象 (log - > false)
Log.e("zpan", "====" + zpDanLiDemo1.equals(zpDanLiDemo2));
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}