菜鸡一只,如果有说错的请大家批评指出,千万别给我留面子,我也不想写出误导新人的文章来!!!
本文来整理整理相对基础的东西:单例模式
该系列有两篇文章:
【java】关于java单例的思考(上) :https://blog.csdn.net/lsr40/article/details/94195394
【java】关于java单例的思考(下):https://blog.csdn.net/lsr40/article/details/94316409
网上其实有许多文章在说单例模式,但是我觉得似乎说的不够全面,所以我想写写我对于单例模式的理解。
1、什么是单例
单例模式其实就是在为了保证一个类(class)仅有一个实例
2、为什么要使用单例
节省多次new和回收对象所消耗的资源和时间
3、单例的实现方式
总的来说:
单例就是一个类的构造方法私有化(private)
并且提供一个静态(static)的getInstance方法,让外部调用,来获得该类的实例化对象
4、单例的分类(网上的文章实在是有点乱,也可能是我水平不够吧)
我暂时了解到的单例实现方式有四种(如果还有其他的希望大家能给我留言,我也会更新进来):
饿汉模式,懒汉模式(也叫饱汉模式),静态内部类模式,单例注册表模式
-1.饿汉模式
顾名思义,就是饿的等不及了,要先创建单例对象
public class IceCreamFactory {
private static final IceCreamFactory iceCreamFactory = new IceCreamFactory();
private IceCreamFactory(){}
public static IceCreamFactory getInstance(){
return iceCreamFactory;
}
}
优点:不需要考虑什么线程安全导致的性能问题,个人觉得这种模式还是比较方便的
缺点:在类被加载的时候,对象就会被实例化占用内存,如果该对象一直未被用到,就比较浪费资源
-2.懒汉模式/饱汉模式
懒加载,饱了,不急着一开始就创建对象
public class IceCreamFactory {
private static IceCreamFactory iceCreamFactory ;
private IceCreamFactory(){}
public static IceCreamFactory getInstance() {
if (iceCreamFactory == null) { //代码1
iceCreamFactory = new IceCreamFactory(); //代码2
}
return iceCreamFactory;
}
}
好,那么问题来了
在多线程的情况下,这么写会出问题吗?会的。
当线程A进入到“代码1”的,iceCreamFactory对象null,然后他就要去执行“代码2”创建对象,这时候刚好,资源被抢,线程B也进入到“代码1”,iceCreamFactory对象还暂时为null,然后线程B就也要去执行“代码2”创建对象。这样就会导致线程A和线程B拿到的对象不是同一个,破坏了单例原则!
然后就是一系列的优化(每一个优化都是为了解决上一个优化的缺点或者问题),我简单说下:
1、加同步方法关键字
2、加同步代码块
3、双重为空判断
4、加上volatile关键词
优化一:在getInstance方法上加上synchronized,就解决了线程安全的问题,但是会带来性能的衰减,因为当有一个线程进入到了方法内,其他所有线程都会被卡在方法外,代码如下:
public class IceCreamFactory {
private static IceCreamFactory iceCreamFactory ;
private IceCreamFactory(){}
public synchronized static IceCreamFactory getInstance() {
if (iceCreamFactory == null) {
iceCreamFactory = new IceCreamFactory();
}
return iceCreamFactory;
}
}
优化二:
方法不做同步,在方法内部需要同步的代码上加上锁,让其他线程可以进到方法内,先执行一些不需要同步的前置代码,加快不同线程的调用速度,但是这样又会有一开始线程不安全的问题(不同线程都判断iceCreamFactory对象null,然后都去new一个新的对象,结果返回不同的对象),代码如下:
public class IceCreamFactory {
private static IceCreamFactory iceCreamFactory ;
private IceCreamFactory(){}
public static IceCreamFactory getInstance() {
if (iceCreamFactory == null) {
synchronized (IceCreamFactory.class) {
iceCreamFactory = new IceCreamFactory();
}
}
return iceCreamFactory;
}
}
优化三:
在同步代码内,在做一次为空判断,这样就算在外面判断为空的线程,进到同步代码块中还会判断一次是否为空,就可以避免优化二会遇到的问题
public class IceCreamFactory {
private static IceCreamFactory iceCreamFactory ;
private IceCreamFactory(){}
public static IceCreamFactory getInstance() {
if (iceCreamFactory == null) {
synchronized (IceCreamFactory.class) {
if (iceCreamFactory == null) {
iceCreamFactory = new IceCreamFactory();
}
}
}
return iceCreamFactory;
}
}
优化四:
但是看似没有问题的优化三,其实还是存在问题的,因为new一个对象,这样的操作不具有原子性,意思就是new虽然只有一行代码,但是实际运行起来确有三个步骤:
1、申请一块内存空间
2、在空间中实例化对象
3、将引用执行这块空间地址(指向之后,就不为null了)
Java内存模型并不限制处理器重排序,因此有可能会因为存在三个步骤的原因出现问题,例如,先执行1,然后执行3,最后执行2。
在线程A还未在空间中实例化完对象,恰好另一个线程进入方法判断该对象引用不为null,然后就将其返回使用,导致出错,所以优化四如下:
public class IceCreamFactory {
private volatile static IceCreamFactory iceCreamFactory ;
private IceCreamFactory(){}
public static IceCreamFactory getInstance() {
if (iceCreamFactory == null) {
synchronized (IceCreamFactory.class) {
if (iceCreamFactory == null) {
iceCreamFactory = new IceCreamFactory();
}
}
}
return iceCreamFactory;
}
}
通过volatile关键词来禁止jvm对该对象的重排优化,这样就最终解决了懒汉模式的多线程和性能的问题。
如果想了解更多重排优化的情况,可以访问:https://www.cnblogs.com/tuhooo/p/7921651.html(作者:tuhooo)
为了不写的太长,本文就到这里。
后面还有两种模式,大家请看下篇:
【java】关于java单例的思考(下):https://blog.csdn.net/lsr40/article/details/94316409
最近其实在看字节码相关的东西,虽然看是看的差不多了,不过一直不知道从何写起,写了又删,删了又写,因此暂时搁置,等我有更好的思路的时候再来编辑它。
还是老话,菜鸡一只(我也要努力学习充实自己啊)!!如果有什么问题,或者写错的,欢迎大家留言讨论与纠错!!