设计模式——单例模式
单例模式:如果程序运行过程中,某个类的实例只有唯一的一份,那么可以说这个类就是单例的,相应的设计模式称谓单例模式。这种模式涉及到一个单一的类,该类自己负责创建自己的实例,同时必须确保这运行过程中只能有一个实例被创建。
单例模式的实现方式有:饿汉式,懒汉式,双重校验式,静态内部类方式,枚举方式,下面具体每一种方式的实现代码并分析优劣。
1-饿汉式
/**
* 单例模式:饿汉式,天然线程安全,在初始化时立即加载这个对象
*/
public class Singleton1 {
//0-私有化构造器
private Singleton1(){}
//1-类初始化时立即加载对象
private static Singleton1 singleton = new Singleton1();
//2-静态方法提供单例对象
public static Singleton1 getSingleton(){
return singleton;
}
}
|
饿汉式:天然线程安全,没有加锁,效率较高,在类加载的时候就初始化实例,可能会产生垃圾对象,没有达到懒加载的效果。
测试代码:
@Test
public void testSingleton1(){
Singleton1 singleton1 = Singleton1.getSingleton();
Singleton1 singleton2 = Singleton1.getSingleton();
//断言两者是同一对象
Assert.assertTrue(singleton1 == singleton2);
}
|
2-懒汉式
/**
* 懒汉式,在需要使用对象时在new出来,有线程安全问题
* 资源利用率提高了,但是每次调用的时候都要同步,并发效率低了
*/
public class Singleton2 {
//0-私有化构造器
private Singleton2(){
//多次调用直接抛出异常,确保单例
if(null != singleton){
throw new RuntimeException("单例不允许创建多个对象");
}
}
//1-设置私有静态属性
private static Singleton2 singleton;
//2-提供获取实例方法,加synchronized关键字则可以保证线程安全
public static synchronized Singleton2 getSingleton(){
if(singleton == null){
singleton = new Singleton2();
}
return singleton;
}
}
|
懒汉式:需要加锁进行同步才能保证线程安全,效率较低,实现了懒加载,在调用的时候才会校验是否需要加载,如果已经加载了则直接返回实例对象,否则调用构造器方法进行初始化并返回。
测试代码:
@Test
public void testSingleton2(){
Singleton2 singleton1 = Singleton2.getSingleton();
Singleton2 singleton2 = Singleton2.getSingleton();
//断言两者是同一对象
Assert.assertTrue(singleton1 == singleton2);
}
|
3-双重校验方式
/**
* 双重检验锁方式:在静态获取单例实例时做了两次判断,且采用synchronized关键字确保单例
*/
public class Singleton3 {
//0-私有化构造器
private Singleton3(){}
//1-设置私有静态属性,volatile关键字保证数据在多个线程间修改是可见的
private volatile static Singleton3 singleton;
//2-提供获取实例方法,双重检验,加synchronized关键字保证线程安全
public static Singleton3 getSingleton(){
if(null == singleton){
synchronized (Singleton3.class){
if(null == singleton){
singleton = new Singleton3();
}
}
}
return singleton;
}
}
|
双重检验方式:采用双锁机制,在多线程下也能有较好的性能。
测试代码:
@Test
public void testSingleton3(){
Singleton3 singleton1 = Singleton3.getSingleton();
Singleton3 singleton2 = Singleton3.getSingleton();
//断言两者是同一对象
Assert.assertTrue(singleton1 == singleton2);
}
|
Spring中也是采用这种方式确保bean在容器中的唯一性(必须在<bean>中没有定义scope的值或者scope属性为singleton)代码如下所示:
DefaultSingletonBeanRegistry.class protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if(singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
Map var4 = this.singletonObjects;
synchronized(this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if(singletonObject == null && allowEarlyReference) {
ObjectFactory singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if(singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject != NULL_OBJECT?singletonObject:null;
}
|
4-静态内部类方式
/**
* 静态内部类实现方式:
* 1、外部类没有static属性,不会像饿汉式那样立即加载对象
* 2、只有真正调用getSingleton(),才会加载静态内部类,加载类时是线程安全的
* 只能被赋值一次
* 3、兼备了并发高效调用和延迟加载的优势
*/
public class Singleton4 {
//0-私有化构造器
private Singleton4() {
}
//1-定义一个静态内部类,持有一个单例实例对象
private static final class Singleton4Holder {
private static final Singleton4 singleton = new Singleton4();
}
//2-获取单例实例方法
public static Singleton4 getSingleton() {
return Singleton4Holder.singleton;
}
}
|
静态内部类方式:利用了静态域延迟初始化,饿汉式是在类加载的时候就初始化实例,但是静态内部类的方式必须在Singleton4Holder被主动使用的时候才会加载实例,线程安全,只适用于静态域的情况。
测试代码:
@Test
public void testSingleton4(){
Singleton4 singleton1 = Singleton4.getSingleton();
Singleton4 singleton2 = Singleton4.getSingleton();
//断言两者是同一对象
Assert.assertTrue(singleton1 == singleton2);
}
|
5-枚举方式
/**
* 枚举方式:
*/
public enum Singleton5 {
INSTANCE;
} |
测试代码:
@Test
public void testSingleton5(){
Singleton5 singleton1 = Singleton5.INSTANCE;
Singleton5 singleton2 = Singleton5.INSTANCE;
//断言两者是同一对象
Assert.assertTrue(singleton1 == singleton2);
}
枚举方式:实现单例的最佳方式,自动支持序列化机制,绝对防止多次实例化对象,而且可以防止反序列化重新创建对象,从JDK1.5之后才有enum特性。