单例模式 java 实现_设计模式(1)——单例模式Java实现

概念

单例模式——确保一个类只有一个实例,并且提供一个全局访问的方法。

为什么要使用单例模式?

我认为学习设计模式的意义在于应用,而不是为了学习而学习,为了面试而死记硬背,因为这样终究会遗忘的。

单例模式的根本是保证在运行的系统中一个类只有一个实例,其他方法或者类都是使用这唯一的实例。生产中最常见的就是资源的管理,例如,只有一个正在打印的任务,只有一个资源的管理器,只有一个订单的ID生成器等等,当然我们在设计系统时可以采用分布式的方式代替单例,不过这样就会引入分布式的解决方案,在小系统中我们更加倾向于使用单例模式。

单例模式的实现要点

定义static的单例对象实例的引用

定义static的访问方法

私有的构造函数

c79938a14beb

单例模式类图

单例模式的Java实现

单例模式的实现方式有很多种,也有各种各样的命名,看上去很复杂,其实抛开教科书一样的理论名称,捋顺思路实现起来很简单的。

首先,在类加载的时候直接初始化,即简单又能保证线程安全,只是因为在不使用的时候初始化可能带来性能上消耗。

public class Singleton {

//直接初始化,每次获取实例时都返回已经初始化好的实例。

private static Singleton instance = new Singleton();

private Singleton(){}

public static Singleton getInstance(){

return instance;

}

}

如果初始化对系统性能有影响,则可以选择在使用时初始化(延迟初始化)。

很明显下面这是一个错误的延迟初始化,在校验instance是否为null 的时候,可能多条线程同时看到null的状态,并且都进行了初始化;也可能某条线程看到非null的状态,而实际上返回的确是一个未初始化完成的实例。多线程问题很难通过结果来推断程序执行期间到底发生了什么,所以定位问题就显得极为困难,尽量在设计编写代码时保证多线程的安全性。

//这是一个错误的延迟初始化

public class Singleton {

private static Singleton instance;

private Singleton(){}

public static Singleton getInstance(){

//线程不安全

if (null == instance) {

instance = new Singleton();

}

return instance;

}

}

既然上面会有线程不安全问题,那么在初始化的时候,我们就应该进行同步,避免多线程同时访问instance实例。

下面的代码在第一次判断为null的时候,对Singleton.class进行加锁同步后进行第二次判断,如果仍旧为null,则进行初始化。

很不幸这段代码也是错误的。这是初学者最容易犯错的,也是大家最迷惑的地方,因为看上去代码是正确的,当多线程同时执行到synchronized代码块时,只有一条线程能够获得锁并且进行初始化,在没有初始化完成前,其他的现在处于等待之中,知道获得锁的线程初始化完成。

问题出现在第一次判断null语句,因为其并不在synchronized代码块中,与第二次判断null的语句不具有先行发生原则Happens-Before,Java虚拟机可能发生重排序操作。简而言之,多线程中可能有线程看到了非null的实例,而实际上得到的是未初始化完成的对象实例,并且将其进行使用,这样后果是不可预知的。

public class Singleton {

private static Singleton instance;

private Singleton(){}

public static Singleton getInstance(){

if (null == instance) {

synchronized (Singleton.class){

if (null == instance){

instance = new Singleton();

}

}

}

return instance;

}

}

解决上面问题的办法是volatile修饰instance。因为在先行发生原则Happens-Before中有一条volatile原则,即对volatile变量的写入操作必须在对该变量的读操作之前执行。Java虚拟机的重排序是在对执行结果的不影响的前提下进行的,有了volatile的修饰,即使虚拟机认为读写顺序对结果不影响,但要满足先行发生原则也不会进行重新排序。这样就可以保证线程看到的是一个初始化完整的实例了。

public class Singleton {

private static volatile Singleton instance;

private Singleton(){}

public static Singleton getInstance(){

if (null == instance) {

synchronized (Singleton.class){

if (null == instance){

instance = new Singleton();

}

}

}

return instance;

}

}

上面就是双重检验加锁的正确实现了,在早些时候加锁对程序性能的损害是很大的,双重检验加锁虽然实现复杂,却是一个很具有性能优势的实现技巧。被很多面试官不断的考察,去验证面试者的水平,加上volatile和synchronized也是Java中很重要的知识点,所以双重检验加锁成了一个热门的技术考察点。

不过现在随着虚拟机技术的不断提升,同步带来的性能损害逐渐降低,人们没有必要编写双重检验加锁这么复杂的单例模式,而直接使用简单的同步策略,在性能上没有影响,而且更容易理解。

synchronized直接修饰getInstance()方法,保证多线性同步获取instance实例。

//推荐的实现方式

public class Singleton {

private static Singleton instance;

private Singleton(){}

public synchronized static Singleton getInstance(){

//线程安全

if (null == instance) {

instance = new Singleton();

}

return instance;

}

}

总结

单例模式是最简单的,也是最常用的设计模式,考试和面试会被高频率考察到。记住本文的单例模式的实现要点,并且跟随单例模式的Java实现章节的思路,一定会对单例模式有一个透彻的理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值