单例模式逆向思维

单例模式逆向思维

说到设计模式,十有八九,singleton模式在面试中排名第一,这一定是大多数人从入门到面试都无法回避的基础知识。
但是,singleton模式中并不只有懒惰模式和饥饿模式,大多数都是最基本的模式。如果你读过spring之类的知名框架源代码,你会发现他们的singleton模式编写和你所知道的完全不一样。
本文将为您带来singleton pattern的基本->最优->附加推荐法,帮助您在面试中获得疯狂分。

又懒又饿
1.饥饿的中国模式

饿模式简单的理解就是提前创建对象。
优点:编写简单,没有线程同步的问题。
缺点:因为对象要提前创建,所以不管用不用,总是占用内存。
建议:如果对象小而简单,使用饿中文模式。

公共最终类Singleton 
{
//创建良好的实例
私有静态Singleton实例= new Singleton();
//构造函数
私有Singleton() {}
//获取实例
公共静态Singleton getInstance() {
返回实例;
    }
}

复制代码

2.懒惰模式

对懒惰模式的简单理解就是在需要的时候创建对象。
优点:懒加载模式性能更高。
缺点:考虑多线程的同步。
推荐:只要不符合上面推荐的饿人条件,就用懒人模式。

公共最终类Singleton {
私有静态单例实例= null
//构造函数
私有Singleton() {}
//获取实例
公共静态Singleton getInstance() {
//仅在对象为空时实例化该对象
if (null == instance) {
instance = new Singleton();
}
返回实例;
    }
}

复制代码

同步锁

懒人的缺点就是多线程同步的问题,你可以马上想到用同步锁来解决这个问题。
这里使用synchronized关键字,通过代码块降低锁的粒度,最大程度的保证了性能开销。其实从java8开始,synchronized的性能已经有了很大的提升。

公共最终类Singleton {
私有静态单例实例= null
//构造函数
私有Singleton() {}
//获取实例
公共静态Singleton getInstance() {
//获取对象时添加同步锁
if (null == instance) {
synchronized (Singleton.class) {
instance = new Singleton();
    }
        }
返回实例;
        }
}

复制代码

双重检查锁

虽然上面用了同步锁代码块,勉强解决了线程同步的问题,最大程度优化了性能开销,但实际上多线程环境下还是存在线程安全问题的。
当仍然有多个线程进入if判断时,这个线程安全问题仍然存在。虽然这种情况不一定会发生,但极端情况下发生的概率很大。
这时候你就需要用到DCL了,就是你在面试中喜欢问的关于设计模式的双检锁模式。听起来很高大上,其实又多了一层判断。
说白了就是进入同步锁前后都检查,大大减少了线程安全的问题。

公共最终类Singleton {
私有静态单例实例= null
//构造函数
私有Singleton() {}
//获取实例
公共静态Singleton getInstance() {
//第一个判断,当instance为null时,实例化对象。
if(null == instance) {
synchronized (Singleton.class) {
//第二次判断,放入同步锁,实例为空时实例化对象
if(null == instance) {
instance = new Singleton();
返回实例;
}

复制代码

最佳双重检查锁

双检锁模式是单懒模式处理多线程下安全问题的最佳方案之一,但仍不是最好的写法。
这里是指令重排的概念,它在java内存模型中。我用最简单的方式帮你理解。
在Java中,一个对象在内存中执行指令的正常顺序是:分配->创建->引用,而在多线程环境中,由于语句的优化,JVM可能会重新排列顺序:分配->引用->创建。
如果出现这种情况,上面的双重检查锁定方法仍然不能解决线程安全问题。
解决方法很简单,只需添加一个volatile关键字。
volatile关键字的作用:保证可见性和有序性。

公共最终类Singleton {
//添加volatile关键字
private volatile static Singleton实例= null
//构造函数
私有Singleton() {}
//获取实例
公共静态Singleton getInstance() {
//第一个判断,当instance为null时,实例化对象。
if(null == instance) {
synchronized (Singleton.class) {
//第二次判断,放入同步锁,实例为空时实例化对象
if(null == instance) {
instance = new Singleton();
返回实例;
}

复制代码

枚举模式

《有效的Java》是Java行业非常受欢迎的一本书。对于想要深入Java领域的程序员来说,没有理由不读这本书。相信很多Java程序员不管有没有看过这本书,都听过。
然而,本书的作者推荐了一种单一案例设计模式,即枚举。
原理很简单。在Java中,枚举类的域在编译后会被声明为static属性,JVM会保证static修饰的成员变量只被实例化一次。

公共类Singleton {

//构造函数
私有Singleton() {
}

//从枚举中获取实例
公共静态Singleton getInstance() {
返回SingletonEnum。singleton . getinstance();
}

//定义枚举
私有枚举SingletonEnum {
独生子女;

私有单例实例;

// JVM保证这个方法只被调用一次
SingletonEnum() {
instance = new Singleton();
公共Singleton getInstance() {
返回实例;
}

复制代码

摘要

最后这里稍微提一下,免得有人觉得设计模式有点负担。
实际上,单例模式非常简单。饿汉模式和懒汉模式在很多开源框架中都有广泛应用,甚至饿汉模式用的更多。比如Java运行时类就是这么做的,简单粗暴。有兴趣的可以自己看看源代码。
这些框架的作者难道没有意识到本文所描述的问题吗?不,不是的。用哪种方式写singleton模式往往要视情况而定,有些理论上会发生的问题在实践中往往可以忽略。这个时候,他们更喜欢用最简单直接的写法。
真正的难点其实是面试。很多关于singleton模式的问题都喜欢问它的编写方法,存在的问题以及最佳方案。说白了就是造核弹,工厂里拧螺丝的面试。目的是了解你对设计模式的理解,从而判断你学习这门学科的态度和造诣。
所以,看完这篇文章,可以试着手动写,了解一下就够了。没必要深究太多,因为Java领域需要花费精力的地方真的太多了。

感觉

最后说一下我的经历。所谓的设计模式当然可以给Java代码本身带来更多的优雅,但是我写了多年的Java代码,普遍感觉Java本身的装饰太多了,优雅往往带来代码本身的负担。
在我参加过的R&D团队中,几乎都能看到很多工程师写的优雅的代码,有些设计模式也写得很好,但明显的问题是可读性越来越差,这要求每个成员对Java都有很高的造诣,甚至在某些情况下会给人力资源带来压力,从实用的角度来看是不合适的。
我更多的建议是:在面试或学习中对设计模式有很好的了解是有益的,但在实践中,尽量使用不太复杂的设计模式,优先考虑简洁直接的代码,这样有利于整个团队的后期维护,甚至可以加快人员变动后新成员对项目的适应。因为工作还是以业绩为导向,简单高效就可以了,可以随心所欲的玩自己的个人项目。

你可能感兴趣的:(单例模式)