以前态度不端正写的博客,对看过之前写的人说一声抱歉!!对不起!!现在抱着一颗认真的心。保证每一篇博文都会认真的完成。
首先讲一下自己所理解的单例模式—独一无二的对象。对于单例,我所能想到的便是spring的注入,很多都是单例模式那样。我认为有几个优点:(1)这样节省了空间。(2)不用每次去new,就会在一定程度上提升效率。(3)不会产生许多垃圾,导致gc进行回收。这一点其实可以归并在(2)之中。
那么接着讲一下几种单例模式。
1.讲一下对象产生的几种方式:
(1)通过new这个关键字,创建对象。
(2)通过反射的方式newInstance()的方式创建对象。
(3)通过序列化的方式创建对象。
为了防止(1)以外的几种方式产生对象,那么我们接下来逐个进行设计。
(1)防止反射的方式获取。思路是通过一个开关来决定构造器的成功与否。
目前的想法是这样,但是既然都可以通过反射,那么肯定也能够改变flag的值,那么肯定也够得到对象(这点不是很明确,如果以后发现了更好的方法,会写入)
(2)关于序列化防止,一般来说可以给SingleTon这个引用变量加上trasient或者static修饰,那么通常实现Serializable接口的话,就并不会写入到流里。
那么还有一种是实现Externalizable接口,那么这个时候就需要对writeExternal(ObjectOutput out)和readExternal(ObjectInput in)方法进行修改。
这一部分的话,会在后面专门写一个博客讲一下序列化。(以后会贴出地址)
那么就开始正文:
1.饿汉—急加载的形式
唯一的缺点可能就在于占用了资源,并且是线程安全的。
2.懒汉—静态内部类懒加载。
这个方式也是比较安全的一种,不会出现线程不安全的问题,并且也是一种懒加载模式,节省了资源。
3.懒汉-double check
这个版本比较多。
(1)v1.0,这是懒加载的形式,但是这样却造成了性能的急速下降。
(2)v2.0,这样是为了提升性能,采取的double check,这样就不用每次都去获得锁。
但是上面两种仍然是不安全的,因为在并发访问的时候可能就会产生多实例了。主要问题是在于无序性上。
对于JVM来说singleTon=new SingleTon()这个过程分为了3步。
(1)给singleton分配内存
(2)在内存中初始化singleton对象
(3)将内存地址赋给singleton变量(这时singleton变量就不为null了)由于JVM的指令重排的存在,那么线程A可能是先进行了(3),(1)、(2)还没进行,然后将工作内存写进了JVM的主内存当中,现在线程A挂起,线程B进入首次判断,这个时候singleTon并不是null,那么这时候就会返回等同于null的singleTon。
为了解决这个问题,采用了volatile关键字。主要是解决了无序性这个问题。
这样double check就可以说是线程安全的懒加载级别。