设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。说白了,设计模式就是一些优秀的使用案例。使用设计模式可以提高代码的重用性、让代码更容易被他人理解(大家都学过,肯定好理解)、保证代码可靠性(都说了是优秀的使用案例,肯定可靠啦)。
单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。
单例模式分为饿汉模式和懒汉模式。还有一种内部类单例模式。
懒汉式:都说是懒汉式了,初始化时就创建了单例对象,以后获取实例都取这个。
/**
* 单例模式之饿汉式:不管三七二十八,初始化的时候就创建了单例对象
*/
public class Singleton1 {
//1.将构造方法私有化,不允许外部直接创建实例
private Singleton1(){}
//2.声明类的唯一实例,使用private static实现
private static Singleton1 instance = new Singleton1();
//3.提供一个用于获取实例的方法,使用public static实现
public static Singleton1 getInstance(){
return instance;
}
}
饿汉式:类初始化时不创建实例,等到真正获取实例时再去判断,只有单例对象为空时,才创建对象
/**
* 单例模式之懒汉式:只有单例对象为空时,才创建对象
*/
public class Singleton2 {
//1.将构造方法私有化,不允许外部之间创建实例
private Singleton2(){
}
//2.声明类的唯一实例,使用private static实现
private static Singleton2 instance;
//3.提供一个用于获取实例的方法,使用public static实现
public static Singleton2 getInstance(){
if(instance==null){
instance = new Singleton2();
}
return instance;
}
}
如上代码所示,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,如何解决?我们首先会想到对getInstance方法加synchronized关键字。
但是,synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,而事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了。
因此,我们可以将synchronized关键字加在方法的内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,这样性能就有一定的提升。
/**
* 将synchronized关键字加到获取实例方法的内部
*/
public class Singleton3 {
//1.将构造方法私有化,不允许外部之间创建实例
private Singleton3(){
}
//2.声明类的唯一实例,使用private static实现
private static Singleton3 instance;
//3.提供一个用于获取实例的方法,使用public static实现
public static Singleton3 getInstance(){
if (instance == null) {
synchronized (instance) {
if (instance == null) {
instance = new Singleton3();
}
}
}
return instance;
}
}
将创建和getInstance()分开,单独为创建单例的方法加synchronized关键字。这样的话我们就可以不用考虑程序的性能问题。
/**
* 单例模式优化,创建和getInstance()分开,单独为创建加synchronized关键字
*/
public class Singleton4 {
//1.将构造方法私有化,不允许外部之间创建实例
private Singleton4 (){
}
//2.声明类的唯一实例,使用private static实现
private static Singleton4 instance = null;
//3.为创建实例的方法添加同步锁
private static synchronized void syncInit() {
if (instance == null) {
instance = new Singleton4 ();
}
}
//4.提供一个用于获取实例的方法,使用public static实现
public static Singleton4 getInstance(){
if (instance == null) {
syncInit();
}
return instance;
}
}
上面两个汉子式的单例,也考虑了线程了,然而尽管这样,还是有可能出问题的,例如:在Java指令中创建对象和赋值操作是分开进行的,也就是说“instance = new Singleton();”语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了。
于是,我们想到使用内部类来维护单例的实现。因为JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。
/**
* 使用内部类来维护单例的实现,保证多线程情况下实例只会被初始化一次。
*/
public class Singleton5 {
//1.将构造方法私有化,不允许外部之间创建实例
private Singleton5 (){
}
//2.此处使用一个内部类来维护单例
private static class SingletonFactory {
private static Singleton5 instance = new Singleton5 ();
}
//3.声明一个使用内部类来获取单例的方法
public static Singleton5 getInstance() {
return SingletonFactory.instance;
}
}
使用内部类来维护单例的例子看似很完美,但是十分完美的东西是没有的,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。所以,我们只能根据实际情况,选择最适合自己应用场景的实现方法。
饿汉式的特点:加载类的时候比较慢,但是运行时获取实例的速度比较快,线程安全。
懒汉式的特点:加载类的时候比较快,但是运行时获取实例的速度比较慢,线程不安全。
synchronized关键字锁定的是对象,在用的时候,一定要在恰当的地方使用(注意需要使用锁的对象和过程,可能有的时候并不是整个对象及整个过程都需要锁)