引言:
这一系列文章,翻译自网络上的文章,不过中间会夹杂着个人的理解,非原创,不过中文应该算是原创。
下面介绍,使用设计模式的一些好处:
1、设计模式是已经在工业生产中使用的,用于解决特定问题的标准方法,如果你在遇到类似问题的时候,能参考相应的设计,会节省你很多的时间。
2、设计模式,往往能提高代码的重用性,会减少你的开发时间。
3、既然大家都知道设计模式,包括从你的类的命名上面,类的使用上面,大家都很清楚的知道相应类的具体功能,就像每个人都约定好的那样。
分类:
java设计模式分为3大类:
1、对象创建
2、结构
3、行为
对象创建设计模式的定义:
1、在特定的问题场景中,创建设计模式会合理的根据特定的问题创建对象。因为对象的创建可能会很复杂,会增加不必要的复杂性,创建设计模式以不同的创建方法解决了这些复杂性。
对象创建设计模式包含的范围:
1、单例
2、工厂
3、抽象工厂
4、建造者
5、原型
本节会讲一下,对象创建中的单例设计模式。
单例设计模式,大部分人第一印象感觉很简单,看完这篇博客,我想你就不会这么认为啦,不同的开发者对于以何种方式实现单例设计模式是有争议的,本文尽可能的把目前实现单例的方法概括出来,并给出最佳实战的建议。
单例模式的定义:
单例模式是一种创建对象实例的设计模式,在创建对象的过程中,他会保证,在一个java虚拟机里面只有一个对象实例,同时单例设计模式,必须提供一个给外部访问该唯一对象的入口。
单例模式的应用场景:
日志、驱动对象、缓存、线程池等等,当然设计模式不是单一存在的,其它设计模式中也会使用单例设计模式来完成一些事情,比如:抽象工厂,建造者,原型,门面等。同时在java核心包里面也有其身影,比如java.lang.runtime.
单例模式的实现方法的共性:
1、提供私有的构造函数,防止外部类直接初始化该对象
2、提供私有的静态变量,当然要是该类的对象实例,唯一的对象实例。
3、public的静态方法,这就是上面说的,提供给外部类获取该类对象的唯一入口。
接下来,我们会以不同的方法,来实现单例设计模式。
【A】饥饿实现
饥饿实现就是,等不急,当类被classloader加载的时候就初始化了该对象,这种实现有个弊端就是,当client不使用该类实例就浪费啦,不过我个人觉得,这种情况不存在,你不用他写他做什么呢?代码测试要有覆盖率的,测试天天在你后面催着喊你把他删掉。
public class EagerInitializedSingleton
{
private static final EagerInitializedSingleton instance = new
EagerInitializedSingleton();
//private constructor to avoid client applications to use constructor
private EagerInitializedSingleton(){}
public static EagerInitializedSingleton getInstance()
{
return instance;
}
}
如果你的单例对象在创建的时候不使用过多的资源,这种方法是可行的。但是大部分的时候,单例对象创建的时候会加载很多的资源文件,比如File system,数据库连接等等。我们应该避免在客户端调用getInstance方法之前创建这个对象,但是这种方法无法提供异常的捕获,异常的抛出,可能在创建对象的时候。
【B】静态块的实现方式
静态块的实现方式和上面的实现方式差不多,但是静态块的实现,可以处理异常。代码如下:
public class StaticBlockSingleton
{
private static StaticBlockSingleton instance;
private StaticBlockSingleton(){}
//static block initialization for exception handling
static
{
try{
instance = new StaticBlockSingleton();
}catch(Exception e)
{
throw new RuntimeException("Exception occured in creating singleton instance");
}
}
public static StaticBlockSingleton getInstance()
{
return instance;
}
}
和饥饿的实现方式一样,当类被classloader加载的时候就初始化了该对象。
【C】延迟创建
顾名思义,就是需要的时候再去创建。
public class LazyInitializedSingleton
{
private static LazyInitializedSingleton instance;
private LazyInitializedSingleton(){}
public static LazyInitializedSingleton getInstance()
{
if(instance == null)
{
instance = new LazyInitializedSingleton();
}
return instance;
}
}
上面这种实现方法,在单线程的环境下是工作良好的,但是,在多线程并发创建对象的时候会出现问题,简单的分析下原因,考虑多线程并发安全的时候,首先要找到共享点,就是共享的对象,然后想象各种执行顺序,你会发现instance这个地方就是共享点,当多线程执行的时候,有可能会创建多个对象,所以该方法不能保证单例实现。
【D】线程安全的创建
为了解决多线程并发创建对象的问题,我们引入Synchronized关键字,这样的话,同一时刻,只有一个对象能访问getInstance方法。
public class ThreadSafeSingleton
{
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton(){}
public static synchronized ThreadSafeSingleton getInstance()
{
if(instance == null)
{
instance = new ThreadSafeSingleton();
}
return instance;
}
}
这种方式是能防止多并发引起的多对象创建问题,但是写代码嘛,总要追求点东西,你说别人装逼也好,卖弄也罢,总之在某一方面比你好。在多并发中,HashMap和ConcurrentHashMap,你是锁住整个HashMap还是HashMap的一个segment这是有质的区别的,所以改进的代码,只朝着一个方向,那就是降低锁的力度,于是乎,下面的代码使用double check来降低了力度,提高了性能。
public static ThreadSafeSingleton getInstanceUsingDoubleLocking()
{
if(instance == null)
{
synchronized (ThreadSafeSingleton.class)
{
if(instance == null)
{
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
还有一种实现叫Bill Pugh,java的版本更新变化很快,包括新出的java9,可能名字不是,大家也知道,金融在未来几年是个很火的领域,于是乎java9中集成了货币的api支持,这是我的猜测,哈哈,包括先在的招财宝,铜板街,挖财,据说王福强大神,跳到挖财啦,当然我也是做金融行业的业务,有兴趣的可以留言,据说要招人。这些题外话,但是不无用处哈,java的内存模型会导致上面的几种方法,在很多很多线程出现的时候,会出现问题,于是乎,这个人,也就是Bill Pugh自己实现了一下这种方法:
【E】Bill Pugh实现
public class BillPughSingleton
{
private BillPughSingleton(){}
private static class SingletonHelper
{
private static final BillPughSingleton INSTANCE = new
BillPughSingleton();
}
public static BillPughSingleton getInstance()
{
return SingletonHelper.INSTANCE;
}
}
看代码发现Bill Pugh是通过私有静态内部类来实现的,当单例对象被classloader加载的时候,SingletonHelper是不会被加载到内存的,除非有对象调用getInstance方法,这是最常用的创建单例对象的方法,因为不需要锁,已经在多个项目中使用了这种方法,简单有效。
【F】Enum实现
public enum EnumSingleton
{
INSTANCE;
public static void doSomething()
{
//do something
}
}
世界上好人和坏人总是都存在的,单例对象也不例外,总有那么几种方法是搞破坏的,费尽千辛万苦,创建的单例,有可能被破坏的。
【破坏者1】反射
【破坏者2】序列化
有破坏办法,就又解决办法,破坏者1的解决办法是ENUM,破坏者2的解决办法是重写readResolve方法。这两部分,你说是和反射有关呢,还是和序列化有关呢,序列化都够我写一篇东西的了。下次再说
poke holes in the abstraction,and it starts leaking.