单例模式的定义:
Singleton pattern restricts the instantiation of a class and ensures that only one instance of the class exists in the java virtual machine. The singleton class must provide a global access point to get the instance of the class.
限制一个类的实例在一个jvm实例中确保只有一个,而且必须提供一个全局访问点获得该单例。
为什么会出现单例模式:
• 减少内存开支。由于单例在内存中相对于一个jvm实例内只有一个实例对象,不会重复的创建和jvm垃圾回收,对于内存减少了空间占用,也利于jvm垃圾回收处理。
• 减少系统性能开销。当一个对象的产生依赖较多的资源时,比如读取配置或者依赖其他对象的时候,单例在jvm启动的时候预加载资源,然后可以永久驻留内存,当然也减少了jvm的垃圾回收线程的负担。
•当然还有很有的优势,现流行的spring框架就是默认支持单例模式(相对应spring容器)。
单例的实现方式:
实现单例模式的方式有很多不同手段,但以下几点我们会同时考虑:
•构造函数必须私有,不能让别人有权限随意实例化
•该单例一般在类中有一个私有静态变量
•该单例一般提供一个静态公共方法获得该单例(对于外界的该单例的全局访问点)
饿汉式(Eager initialization)
饿汉式单例实现方式是在类加载的时候初始化该单例。这种方式实现单例最简单,但也有个缺点就是即使我们应用中没有使用该类的单例,但类加载的时候也必须初始化。
package com.doctor.design_pattern.singleton;
/**
* @author sdcuike
*
* Created on 2016年7月31日 下午11:36:05
*
* 饿汉式 单例
* EagerInitialized Singleton
*/
public class EagerInitializedSingleton {
private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
private EagerInitializedSingleton() {
}
public static EagerInitializedSingleton getInstance() {
return instance;
}
public void doSomething() {
System.out.println("test");
}
public static void main(String[] args) {
System.out.println(EagerInitializedSingleton.getInstance());
System.out.println(EagerInitializedSingleton.getInstance());
EagerInitializedSingleton.getInstance().doSomething();
// com.doctor.design_pattern.singleton.EagerInitializedSingleton@2a139a55
// com.doctor.design_pattern.singleton.EagerInitializedSingleton@2a139a55
// test
System.out.println(EagerInitializedSingleton.getInstance() == EagerInitializedSingleton.getInstance());
// true
}
}
当单例没有涉及到过多的资源使用的时候,饿汉式单例比较适合。但很多场景下,单例的使用一般是为了使用一些资源比如文件系统、数据库连接等等,这中场景下,一般我们尽量使得该单例必须使用的时候,才会初始化,以避免资源的浪费使用。而且饿汉式单例也没提供异常的处理机制。
Static block initialization
静态块初始化单例和饿汉式单例相似,差别在于实例的初始化在静态块中,这中方式提供了异常处理。
package com.doctor.design_pattern.singleton;
/**
* @author sdcuike
*
* Created on 2016年8月1日 上午12:30:08
*/
public class StaticBlockSingleton {
private static StaticBlockSingleton instance;
public static StaticBlockSingleton getInstance() {
return instance;
}
static {
try {
instance = new StaticBlockSingleton();
} catch (Exception e) {
throw new RuntimeException("Exception occured in creating singleton instance");
}
}
public static void main(String[] args) {
System.out.println(StaticBlockSingleton.getInstance());
System.out.println(StaticBlockSingleton.getInstance());
System.out.println(StaticBlockSingleton.getInstance() == StaticBlockSingleton.getInstance());
// com.doctor.design_pattern.singleton.StaticBlockSingleton@2a139a55
// com.doctor.design_pattern.singleton.StaticBlockSingleton@2a139a55
// true
}
}
Lazy Initialization
懒初始化方法承担了单例的创建,并且是获取该单例的入口点。
package com.doctor.design_pattern.singleton;
/**
* @author sdcuike
*
* Created on 2016年8月1日 上午12:39:40
*/
public class LazyInitializedSingleton {
private static LazyInitializedSingleton instance;
public static LazyInitializedSingleton getInstance() {
if (instance == null) {
instance = new LazyInitializedSingleton();
}
return instance;
}
private LazyInitializedSingleton() {
}
public static void main(String[] args) {
System.out.println(LazyInitializedSingleton.getInstance() == LazyInitializedSingleton.getInstance());// true
}
}
上面几种单例的实现在单线程环境下可以很好的工作,但可能面临多线程安全问题。
Thread Safe Singleton
线程安全单例,简单的线程安全我们可以用
synchronized实现。
package com.doctor.design_pattern.singleton;
/**
* @author sdcuike
*
* Created on 2016年8月1日 上午11:19:36
*
* Thread Safe Singleton
*
*/
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
public static synchronized ThreadSafeSingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
return instance;
}
private ThreadSafeSingleton() {
}
public static void main(String[] args) {
System.out.println(ThreadSafeSingleton.getInstance() == ThreadSafeSingleton.getInstance());
// true
}
}
上面的线程安全锁的粒度是比较大了,锁的粒度越大,并发性约不能,导致性能下降,我们可以用double checked locking 规则减少锁的粒度。
package com.doctor.design_pattern.singleton;
/**
* @author sdcuike
*
* Created on 2016年8月1日 上午11:37:25
*
* double checked locking principle
*/
public class DoubleCheckedLockingThreadSafeSingleton {
private static DoubleCheckedLockingThreadSafeSingleton instance;
public static DoubleCheckedLockingThreadSafeSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedLockingThreadSafeSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedLockingThreadSafeSingleton();
}
}
}
return instance;
}
private DoubleCheckedLockingThreadSafeSingleton() {
}
public static void main(String[] args) {
}
}
Inner static helper class Singleton
采用内部类来实现资源的懒加载:
package com.doctor.design_pattern.singleton;
/**
* @author sdcuike
*
* Inner static helper class Singleton
*
*
* Created on 2016年8月1日 上午11:54:05
*/
public class InnerStaticHelperClassSingleton {
private InnerStaticHelperClassSingleton() {
}
public static InnerStaticHelperClassSingleton getInstance() {
return SingletonHelper.instance;
}
private static class SingletonHelper {
private static final InnerStaticHelperClassSingleton instance = new InnerStaticHelperClassSingleton();
}
public static void main(String[] args) {
}
}
内部类拥有外部类的实例,当外部类被jvm加载的时候,内部类没有被加载,外部类的实例也就没有被实例化,当外部类真正的调用得到单例入口方法的时候,才会触发内部类的加载,同时外部类的单例也就初始化一次。
这种方式的好处就是不用锁同步,就可以实现线程安全的单例。
枚举单例实现Enum Singleton
上面的单例实现可能由于反射导致不能保证一个类只有一个实例。下面的枚举实现方式克服了这个问题,但这种实现放手不能延迟加载。
package com.doctor.design_pattern.singleton;
/**
* @author sdcuike
*
* Enum Singleton
* 枚举实现单例不能延迟加载资源,但保证了enum值只实例化一次。而且克服了反射带来的问题
*
* Created on 2016年8月1日 下午12:24:06
*/
public enum EnumSingleton {
instance;
private EnumSingleton() {
}
public void doSomething() {
System.out.println("test do ");
}
public static void main(String[] args) {
EnumSingleton.instance.doSomething();
}
}
序列化与单例
我们一般通过网络交互的时候都会用到序列化,序列化打破了单例的规则,反序列化我们得到了另一个实例(请自行验证)。为了保证序列化不影响单例规则,我们一般实现以下方法:
protected Object readResolve() {
return getInstance();
}