最近阅读了《大话设计模式》,本人感觉书中讲的简单易懂,并对书中的Demo做了Java实现,下面我将从这本书中总结一下几种常用的设计模式,首先看最常用的单例模式:
保证类仅有一个实例,并提供一个访问它的全局访问点。
单例模式的实现分为饿汉式单例和懒汉式单例,为什么会分这两种,我们来看:
/**
* 饿汉式单例(立即加载,会过早地创建实例,降低内存的使用效率)
*/
public class Singleton {
// 指向自己实例的私有静态引用,主动创建
private static Singleton ourInstance = new Singleton();
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton getInstance() {
return ourInstance;
}
//私有的构造方法
private Singleton() {}
}
以上是饿汉式单例模式的实现,它能实现单例的原因是:这个类包含静态私有的实例域和构造方法,外部只能使用这个类提供的公有方法获得其对象实例,而这个实例是在类加载时创建好的唯一实例。
/**
* 懒汉式单例(延迟加载,实现按需创建实例) 多线程下可能同时创建实例
*/
public class Singleton2{
// 指向自己实例的私有静态引用
private static Singleton2 singleton2;
// 私有的构造方法
private Singleton2(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton2 getSingleton2(){
// 被动创建,在真正需要使用时才去创建
if (singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
以上是懒汉式单例模式的实现,它能实现单例的原因同样是:这个类包含静态私有的实例域和构造方法,外部只能使用这个类提供的公有方法获得其对象实例,但大家注意,这个类生成唯一实例的时机是外部调用其提供的公有方法时才创建实例。
所以, 懒汉式单例相比饿汉式单例可实现延迟加载(在new时才会加载类),实现按需创建,提高内存的使用率。
从上面来看已经实现了单例模式,但大家忽略了一个问题,在多线程场景下它们是否还能正常创建唯一实例。我们分别来看:
/**
* 饿汉式单例(立即加载,会过早地创建实例,降低内存的使用效率)
*/
public class Singleton {
// 指向自己实例的私有静态引用,主动创建
private static Singleton ourInstance = new Singleton();
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton getInstance() {
return ourInstance;
}
//私有的构造方法
private Singleton() {}
}
我们分析一下:假设多个线程都执行到这个类的getInstance方法,因为实例在类加载的时候就已经创建好,所以 这些线程拿到的都是同一实例。所以,我们可以得到,饿汉式单例是线程安全的。
/**
* 懒汉式单例(延迟加载,实现按需创建实例) 多线程下可能同时创建实例
*/
public class Singleton2{
// 指向自己实例的私有静态引用
private static Singleton2 singleton2;
// 私有的构造方法
private Singleton2(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton2 getSingleton2(){
// 被动创建,在真正需要使用时才去创建
if (singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
我们再看懒汉式,我们假设一个场景,多个线程同时执行到getSingleton2方法的if语句,这时,它们都通过了判断,认为sinleton2是null,所以会new出许多对象实例,并不能实现预想的单例。所以,我们可以得出,懒汉式单例是线程不安全的。
那我们怎么解决线程不安全问题呢?
我们提供一种解决方案,看下面这段代码:
/**
* 多线程下线程安全的懒汉式单例
* 1.同步延迟加载 — synchronized方法(方法锁/对象锁)
* 这种实现方式的运行效率会很低,因为同步块的作用域有点大,而且锁的粒度有点粗。同步方法效率低,那我们考虑使用同步代码块来实现。
*/
public class Singleton3{
private static Singleton3 singleton3;
private Singleton3(){}
// 使用 synchronized 修饰,临界资源的同步互斥访问
public static synchronized Singleton3 getSingleton3(){
if (singleton3 == null) {
singleton3 = new Singleton3();
}
return singleton3;
}
}
通过synchronized给静态方法加锁,可以实现多线程同步访问getSingleton3方法,这样就可以避免生成多个实例。 但是,这种实现方式的运行效率会很低,因为同步块的作用域有点大,而且锁的粒度有点粗。同步方法效率低。 因此,我们提供另外一种改进方法:
双重检查(Double-Check Locking)
/**
* 双重检查(Double-Check Locking)
*/
class Singleton6{
private static Singleton6 singleton6;
private Singleton6() {}
public static Singleton6 getSingleton6() {
// 双重检查,防止过多的同步,只在第一次创建实例时进行同步
if (singleton6 == null) {
synchronized (Singleton3.class) { // 1
// 只需在第一次创建实例时才同步
if (singleton6 == null) { // 2
singleton6 = new Singleton6(); // 3
}
}
}
return singleton6;
}
}
至此,我们完成了单线程与多线程情况下的单例模式实现,并进行了优化。单例模式在spring中的bean管理,数据库连接,工具类中有大量应用。