单例模式

Posted Alance

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式相关的知识,希望对你有一定的参考价值。

1.单例模式是什么

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通
单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

2.单例模式解决了什么问题

1.节省资源

节省内存资源,减少无谓GC的消耗.

2.防止冲突

如果一个项目中有多个实例,会造成系统冲突。保证同一时间状态唯一。

3.单例模式用法

1.恶汉单例

恶汉模式:加载时就初始化对象。

1.静态代码块初始化

public class HungrySingleton {
	 private static  HungrySingleton hungrySingleton;

	 static {
		 hungrySingleton = new HungrySingleton();
	 }

	 private HungrySingleton (){}

	 public static HungrySingleton getInstance() {  
     return hungrySingleton;  
     }
}

2.常量初始化

public class HungrySingleton {
	 private static final HungrySingleton hungrySingleton = new HungrySingleton();

	 private HungrySingleton (){}

	 public static HungrySingleton getInstance() {  
     return hungrySingleton;  
     }
}

  此方法由于成员变量修饰为final所以优于静态代码块初始化。但是这两种单例模式有共同的缺点。在类加载时完成初始化将占用一定内存空间,占用的内存空间不会被GC回收。如果项目中一次都没有使用该对象。那么该对象会造成内存浪费。那么我们就需要一种‘懒加载’来实现加载。

2.懒汉单例

懒汉模式:调用时才初始化对象。

1.标准懒汉单例

public class StandardLazySingleton {
	private static StandardLazySingleton standardLazySingleton;

	/**
	 * 私有构造方法
	 */
	private StandardLazySingleton() {
	}
	public static StandardLazySingleton getInstance() {
		if (standardLazySingleton == null) {
			standardLazySingleton = new StandardLazySingleton();
		}
		return standardLazySingleton;
	}
}

  此种方式的单例模式拥有静态成员变量,可能会出现并发问题。下面是几种变种解决方案。

2.并发环境下懒汉单例

  直接在获取实例方法上加锁

public class LazySingleton {

	private static LazySingleton lazySingleton;

	private LazySingleton() {
	}
	public static synchronized LazySingleton getInstance() {
		if (lazySingleton == null) {
			lazySingleton = new LazySingleton();
		}
		return lazySingleton;
	}

}

  此方式解决了‘懒加载’的问题,也解决了同步的问题。但是方法上全部加同步造成的效率很低。大多数情况下方法是不需要同步的。

缩小锁定范围.

public class LazySingleton {

	private static LazySingleton lazySingleton;

	private LazySingleton() {
	}
	public static LazySingleton getInstance() {
		if (lazySingleton == null) {
			synchronized(LazySingleton.class) {
				lazySingleton = new LazySingleton();
			}
		}
		return lazySingleton;
	}

}

  此方式缩小了锁定范围,但是同步会出现问题。假如一个线程进入了if (lazySingleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。我们可以在内部再次添加一个if (lazySingleton == null) 的判断。


public class LazySingleton {

	private static volatile LazySingleton lazySingleton;

	private LazySingleton() {
	}
	public static LazySingleton getInstance() {
		if (lazySingleton == null) {
			synchronized(LazySingleton.class) {
				if (lazySingleton == null) {
					lazySingleton = new LazySingleton();
				}
			}
		}
		return lazySingleton;
	}
}

  以上方式为懒汉模式中推荐的单例使用方式。解决了线程同步的问题,提高了效率。这种方式有个专业的名词叫双重检查(Double-Check)

volatile 关键词的作用:对变量的单次读/写操作可以保证原子性。

  首先我们需要了解jvm实例化一个对象需要哪些过程。
1.分配内存空间。2.初始化对象。3.将内存空间的地址赋值给对应的引用。 由于jvm会对指令进行重新排序2和3的顺序可能打乱。当3在2之前时,在多线程环境下可能会暴露出一个没有对象的引用,从而导致不可预料的后果。为了防止这个问题,我们需要将变量设置为volatile类型的变量。保证其指令的原子性。需要特别注意的是volatile关键字在java1.5之前并不能产生这个功能。

3.静态内部类单例

public class StaticInnerClassSingleton {

	private static class SingletonHolder {
		private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
	}

	private StaticInnerClassSingleton() {
	}

	public static final StaticInnerClassSingleton getInstance() {
		return SingletonHolder.INSTANCE;
	}
}

  一个类的静态属性只会在第一加载类时初始化。这是jvm保证的。所以无需担心并发问题。初始化进行一半的时候,别的线程是无法使用的,因为JVM会帮我们强行同步这个过程。静态变量智慧初始化一次,所以也能保证单例。静态内部类方式在StaticInnerClassSingleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类,从而完成StaticInnerClassSingleton的实例化。实现了‘懒加载’。但是如果我们访问了其他静态属性这个也会被实例化。

4.容器单例

  大名鼎鼎的spring就是将每一个实现类作为一个bean装入容器中从而实现了单例。下面我们山寨一个简易的容器方式的单例。此种方式能解决单例中的大部分问题,但是不能解决懒加载的问题。然而这种方式极大的降低了代码之间的耦合。

public class ContainerSingleton {

	private static Map<String, Object> objMap = new HashMap<String, Object>();

	private ContainerSingleton() {
	}

	public static void registerService(String key, Object instance) {
		if (!objMap.containsKey(key)) {
			objMap.put(key, instance);
		}
	}
	public static Object getService(String key) {
		return objMap.get(key);
	}
}

需要在初始化项目的过程中将单例放入容器。

5.枚举单例

  

public enum SomeThing {
    INSTANCE;
    private Resource instance;
    SomeThing() {
        instance = new Resource();
    }
    public Resource getInstance() {
        return instance;
    }
}

  需要用SomeThing.INSTANCE.getInstance()。首先,在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。
也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。
可以看到,枚举实现单例还是比较简单的,除此之外我们再来看一下Enum这个类的声明:

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable

  可以看到,枚举也提供了序列化机制。保证序列化和反序列化是一个对象。
可以这么说,单元素枚举是目前实现单例模式的最佳方法。

4.单例模式的问题

1.不同类加载产生多个实例

(巨坑待填)

2.序列化反序列化产生多个实例

(巨坑待填)

5.单例模式总结

  单例模式是设计模式中相对简单的设计模式,简单的设计模式也有许多复杂的逻辑.在不同的场景中有不同的用法和坑。面对技术要保持谦虚的心态,戒骄戒躁扎实基础。

引用

http://blog.csdn.net/yy254117440/article/details/52305175
https://www.cnblogs.com/zhaoyan001/p/6365064.html
https://www.cnblogs.com/zuoxiaolong/p/pattern2.html

以上是关于单例模式的主要内容,如果未能解决你的问题,请参考以下文章

常用代码片段

性能比较好的单例写法

片段作为 Android 中的单例

单例片段或保存网页视图状态

你熟悉的设计模式都有哪些?写出单例模式的实现代码

单例模式以及静态代码块