单例模式
1.什么是单例模式
单例模式实现在应用程序中,对象有且仅有一个实例对象,诸如系统的设置等组件,通常被设计成单例模式。
2.单例模式的核心原理
实例单例模式,要保证其在整个系统中其对象实例有且仅有一个。
私有构造函数,禁止非法实例化
对于高级程序设计语言(C++,Java)而言,对象实例化是通过new关键字来创建该对象的一个实例,要想保证其不能被第三者多次实例化,其构造方法因此要设计为私有的,这样,在别的类里就不能通过new关键字来创建该类的实例了。
private Singleton(){}
创建并存储静态实例对象引用
创建并存储其自身的一个实例对象,保证该实例对象在应用程序员的生命过程中,只有这一个本类对象。
private static Singleton instance;
提供公有的静态的获取其实例的方法
提供一个外部可以获取其单一实例的方法。
public static Singleton getInstance()
3.单例模式的实现方式——饿汉式
所谓饿汉式,就是在类初始化时就实例化其对象。
public final class SimpleSingleton
{
//持有自身静态实例对象,并在类加载时初始化。
private static final SimpleSingleton INSTANCE = new SimpleSingleton();
//私有化构造函数,禁止外部实例化
private SimpleSingleton()
{
}
//提供给外部的可以获取其实例的静态方法
public final static SimpleSingleton getInstance()
{
return INSTANCE;
}
}
饿汉式虽然能保证其始终只有一个实例对象,但由于其的实例化时机是在类加载时,如果这个实例化过程是一个高消费的过程,而整个实例在某次程序使用中并没有并使用到,就会造成性能的浪费,因此,单例模式产生了一种新的实现方式————懒汉式。
4.单例模式的实现方式——懒汉式
所谓饿汉式,就是在类第一次被使用时,才实例化其对象。
public class LazySimpleSingleton
{
//持有自身静态实例对象,并在类加载时初始化。
private static LazySimpleSingleton INSTANCE;
//私有化构造函数,禁止外部实例化
private LazySimpleSingleton()
{
}
//提供给外部的可以获取其实例的静态方法
public static LazySimpleSingleton 在a时刻调用了getInstance()
{
//--如果实例尚未被初始化,则进行初始化
if (null == INSTANCE)
{
INSTANCE = new LazySimpleSingleton();
}
return INSTANCE;
}
}
一眼看上去该实现试没有什么问题,在单线程模式下,这样实现的单例模式确实没有什么问题,但是一但参与多线程程序,该实现方式也就暴漏出其问题所在了,它不再能保证其只有一个实例对象,这是为什么呢?我们简单来模拟一下。
假设有一个线程A,在a时刻调用了getInstance()方法,并通过(null == INSTANCE)判断,结果为true,进入到if(true)语句块,在它则进入到该语句块时,CPU发了个指令,说:A你先等一下,刚老总来信说,他一个亲信要来办点事,你让他先办。说话中老总的亲信线程B就到了,他即刻调用了getInstance()方法,并且了通过了(null == INSTANCE)判断,结果为true,进入到if(true)语句块,并通过new关键字,创建了LazySimpleSingleton的一个实例对象,并拿到该实例引用满意的走了。这时,线程A一肚子的火的也通过new关键字,创建了LazySimpleSingleton的另一个实例对象,很不痛快的离开了……
经过上述的模拟程序,LazySimpleSingleton被实例化了两次,所以,在多线程并发的条件下,该实现无法保证其实例的单一性,设计上存在缺陷。为了解决在多线程条件下的单一实例,饿汉式找到了CPU的主管,从他那借了一把同步锁,来保证其实例的单一性。
5.单例模式的实现——同步锁
同步锁(java)的作用是实现被同步锁(synchronized)标记的语句块(方法块)在执行时是CPU单一性的,不可分割的,也就是说,在某一线程中执行同步锁标记的语句块(方法块),其执行是单一的,不可再分割的,因此也就保证了实例化过程只能执行一次。
public class LazyThreadSimpleSingleton
{
//持有自身静态实例对象,并在类加载时初始化。
private static LazyThreadSimpleSingleton INSTANCE;
//私有化构造函数,禁止外部实例化
private LazyThreadSimpleSingleton()
{
}
//提供给外部的,加入同步机制的,可以获取其实例的静态方法
public synchronized static LazyThreadSimpleSingleton getInstance()
{
if (null == INSTANCE)
{
INSTANCE = new LazyThreadSimpleSingleton();
}
return INSTANCE;
}
}
这一种在方法块上加载同步锁的机制虽然可以避免在多线程模式下多实例化的问题,但这种实现每次在获取实例时,都使用了锁机制,大大的降低了程序的性能,因此,有了优化后的同步锁实现方式——双重否定式。
6.单例模式的实现——双重否定
双重否定的实现思路,是在方法块同步的锁的基础上优化而来的,把锁应用在实例对象的初始化过程中,而不是每一次获取实例对象时,这样就避免了每次获取实例都上锁的性能消耗。
public class LazyThread2NullSimpleSingleton
{
private static LazyThread2NullSimpleSingleton INSTANCE;
private LazyThread2NullSimpleSingleton()
{
}
public static LazyThread2NullSimpleSingleton getInstance()
{
if (null == INSTANCE)
{
//对实例的初始化过程进行锁机制
synchronized (LazyThread2NullSimpleSingleton.class)
{
if (null == INSTANCE)
{
INSTANCE = new LazyThread2NullSimpleSingleton();
}
}
}
return INSTANCE;
}
}
这样的实现方式,即避免了饿汉式的消费浪费,也实现了多线程模式下的单一实例,但由于其实现条件过多,在实际开发中,很有可能因为人为原因导致bug产生。如果了解JVM原理的话,就可以实现类属性(static)是在类加载时被JVM实例化的,而这个过程是单一不可分割的,因此,采用静态内部类的特性,可以更简单的实现单例模式的懒加载过程。
7.单例模式的实现——静态内部类
利用JVM的特性,用静态内部的类实现单一实例的懒加载过程。
public class LazyHolderSingleton
{
private LazyHolderSingleton()
{
}
//静态内部类,持有其实例对象,在该类被JVM加载时实例化。
private static class LazyHolder
{
static LazyHolderSingleton instance = new LazyHolderSingleton();
}
public static LazyHolderSingleton getInstance()
{
return LazyHolder.instance;
}
}