前言单例模式可以说是设计模式中最简单最基础的设计模式了。就算是初级开发,问到用过哪些设计模式,估计大部分都会说单例模式。但是你认为这样一个基本的“单例模式”真的有那么简单吗?也许你会问:“一个简单的单例模式应该是什么样的?”哈哈,话不多说,让我们拭目以待,坚持看完,相信你一定有所收获!饿了么中式饿了么中式是最常见也是最不需要考虑太多单例模式的,因为它不存在线程安全问题,饿了么也是在类实例对象创建时加载的。饿了么写法如下:publicclassSingletonHungry{privatestaticSingletonHungryinstance=newSingletonHungry();privateSingletonHungry(){}privatestaticSingletonHungrygetInstance(){returninstance;}}测试代码如下:classA{publicstaticvoidmain(String[]args){IntStream1,5rangeClosed(.forEach(i->{newThread(()->{SingletonHungryinstance=SingletonHungry.getInstance();System.out.println("instance="+instance);}).start();});}}结果优势:线程安全,不用关心并发问题,写法也是最简单的。缺点:在加载类的时候会创建对象,也就是说无论你是否使用对象都会创建对象,浪费内存空间。在线程的情况下,这种方式是完美的,但是我们实际的程序执行基本不可能是单线程的,所以这种写法肯定存在线程安全问题if(null==instance){returnnewSingletonLazy();}returninstance;}}多线程执行的演示classB{publicstaticvoidmain(String[]args){IntStream.rangeClosed(1,5).forEach(i->{newThread(()->{SingletonLazyinstance=SingletonLazy.getInstance();System.out.println("instance="+instance);}).start();});}}结果很明显,得到的实例对象是不是单例的。也就是说,这种写法不是线程安全的,DCL(doublechecklock)不能用于多线程的情况。DCL,即DoubleCheckLock,就是在创建实例时进行双重检查。首先,检查实例对象是否为空。如果不为空,则锁定当前类,然后判断实例是否为空,如果仍然为空,则创建实例;代码如下:}}}returninstance;}}测试代码如下:classC{publicstaticvoidmain(String[]args){IntStream.rangeClosed(1,5).forEach(i->{newThread(()->{SingleTonDclinstance=SingleTonDcl.getInstance();System.out.println("instance="+instance);}).start();});}}因此,相信大部分初学者在接触到这种写法的时候就已经有了“高大上”的感觉。首先要判断实例对象是否为空。如果为空,则使用对象的Class作为锁,保证同一时间只能有一个线程访问,然后再次判断实例对象是否为空,最后初始化创建实例对象。一切看起来都没有破绽,但是当你学会了JVM,你可能一眼就看出其中的窍门了。没错,问题出在instance=newSingleTonDcl();因为这不是一个原子操作,所以这句话的执行在JVM层面分为以下三个步骤:1.为SingleTonDcl分配内存空间2.初始化SingleTonDcl实例3.设置实例对象指向分配的内存空间(实例为空)。一般情况下,以上三个步骤是顺序执行的,但实际上,JVM可能会“热情地”优化我们的代码,可能的执行顺序是1、3、2,如下代码所示==实例){同步(SingleTonDcl.class){如果(空==实例){1。为SingleTonDcl分配内存空间3.将实例对象指向分配的内存空间(instance不再为null)2.初始化SingleTonDcl实例}}}returninstance;}假设现在有两个线程t1,t2如果t1执行到上面第3步被挂起,然后t2进入getInstance方法,因为t1执行了第3步,此时instance不再为空,所以if(null==instance)的条件不为空,instance为直接返回了,但是因为t1还没有执行到step2,此时的instance其实是一个半成品,会导致不可预知的风险!如何解决?由于问题在于指令可能重新排序,因此不让它重新排序就足够了。volatile不就是为了这个吗?我们可以在实例变量前面加一个volatile修饰符画外音:volatile的作用1.保证对象内存可见性2.防止指令重排序优化代码如下publicclassSingleTonDcl{privateSingleTonDcl(){}//在前面加volatile关键字对象的volatileprivatestaticSingleTonDclinstance=null;publicstaticSingleTonDclgetInstance(){if(null==instance){synchronized(SingleTonDcl.class){if(null==instance){instance=newSingleTonDcl();}}}returninstance;}}这里好像已经解决了问题,双锁机制+volatile其实基本解决了线程安全问题,保证是“真正的”单身人士,但真的是这样吗?继续往下看静态内部类,先看代码}}测试代码如下:.out.println("instance="+instance);}).start();});}}静态内部类的特点:这种写法使用JVM类加载机制,保证线程安全;由于SingleTonStaticInnerClass是私有的,除了getInstance()之外没有办法访问它,所以它是惰性的;同时读取实例时没有同步,没有性能缺陷;它不依赖于JDK版本;然而,它仍然不完美。不安全的单例实现并不完美,主要有两个原因:1.反射攻击首先要提到java中让人又爱又恨的反射机制。说明一下,这里是一个DCL的例子(为什么选择DCL是因为很多人认为DCL的写法最高....这里开始“打脸”),修改上面的DCL测试代码如下:classC{publicstaticvoidmain(String[]args)throwsNoSuchMethodException,IllegalAccessException,InvocationTargetException,InstantiationException{Class
