黑马程序员技术交流社区

标题: 【成都校区】关于单例的一些小笔记分享 [打印本页]

作者: Morn明    时间: 2018-11-29 13:57
标题: 【成都校区】关于单例的一些小笔记分享
package 尝试.DanLiThreadSafe;//Thread [θred] 线程
/*public class Singleton2 {
    private Singleton2(){
    }
    private static Singleton2 instance;
    public static Singleton2 getInstance(){
        if(instance == null) {//1:读取instance的值
            instance = new Singleton2();//2: 实例化instance
        }
        return instance;
    }
}
*/
/*对于以上代码注释部分,如果此时有两个线程,线程A执行到1处,读取了instancenull
然后cpu就被线程B抢去了,此时,线程A还没有对instance进行实例化。
因此,线程B读取instance时仍然为null,于是,它对instance进行实例化了。
然后,cpu就被线程A抢去了。此时,线程A由于已经读取了instance的值并且认为它为null,所以,
再次对instance进行实例化。所以,线程A和线程B返回的不是同一个实例。
那么,如何解决呢?

1.在方法前面加synchronized修饰。这样肯定不会再有线程安全问题。*/

/*public class Singleton2 {
    private Singleton2(){
    }
    private static Singleton2 instance;
    public static synchronized Singleton2 getInstance(){
        if(instance == null) {//1
            instance = new Singleton2();//2
        }
        return instance;
    }
}*/
/*
但是,这种解决方式,假如有100个线程同时执行,那么,每次去执行getInstance方法时都要先获得锁再去执行方法体,
如果没有锁,就要等待,耗时长,感觉像是变成了串行处理。因此,尝试其他更好的处理方式。*/

/*2.加同步代码块,减少锁的颗粒大小。我们发现,只有第一次instancenull的时候,才去创建实例,
而判断instance是否为null是读的操作,不可能存在线程安全问题,因此,
我们只需要对创建实例的代码进行同步代码块的处理,
也就是所谓的对可能出现线程安全的代码进行同步代码块的处理。*/
/*public class Singleton2 {
    private Singleton2(){
    }
    private static Singleton2 instance;
    public static Singleton2 getInstance(){
        if(instance == null) {
            synchronized (Singleton2.class){
                instance = new Singleton2();
            }
        }
        return instance;
    }
}*/
/*
但是,这样处理就没有问题了吗?同样的原理,线程A和线程B,线程A读取instance值为null
此时cpu被线程B抢去了,线程B再来判断instance值为null,于是,它开始执行同步代码块中的代码,
instance进行实例化。此时,线程A获得cpu,由于线程A之前已经判断instance值为null
于是开始执行它后面的同步代码块代码。它也会去对instance进行实例化。

这样就导致了还是会创建两个不一样的实例。
那么,如何解决上面的问题。
很简单,在同步代码块中instance实例化之前进行判断,如果instancenull,才对其进行实例化。
这样,就能保证instance只会实例化一次了。也就是所谓的双重检查加锁机制。

再次分析上面的场景:

线程A和线程B,线程A读取instance值为null,此时cpu被线程B抢去了,线程B再来判断instance值为null
于是,它开始执行同步代码块代码,对instance进行了实例化。这是线程A获得cpu执行权,
当线程A去执行同步代码块中的代码时,它再去判断instance的值,由于线程B执行完后已经将这个共享资源instance实例化了,
所以instance不再为null,所以,线程A就不会再次实行实例化代码了。
*/
/*public class Singleton2 {
    private Singleton2(){
    }
    private static Singleton2 instance;
    public static synchronized Singleton2 getInstance(){
        if(instance == null) {
            synchronized (Singleton2.class){
                if (instance == null){
                    instance = new Singleton2();
                }
            }
        }
        return instance;
    }
}*/
/*
但是,双重检查加锁并不代码百分百一定没有线程安全问题了。因为,这里会涉及到一个指令重排序问题。
instance = new Singleton2()其实可以分为下面的步骤:
1.申请一块内存空间;
2.在这块空间里实例化对象;
3.instance的引用指向这块空间地址;

指令重排序存在的问题是:

对于以上步骤,指令重排序很有可能不是按上面123步骤依次执行的。比如,
先执行1申请一块内存空间,然后执行3步骤,instance的引用去指向刚刚申请的内存空间地址,
那么,当它再去执行2步骤,判断instance时,由于instance已经指向了某一地址,它就不会再为null了,
因此,也就不会实例化对象了。这就是所谓的指令重排序安全问题。那么,如何解决这个问题呢?

加上volatile关键字,因为volatile可以禁止指令重排序。volatile  [ˈvɒlətaɪl]易变的,不稳定的;
*/
public class Singleton2 {
    private Singleton2() {
    }
    private static volatile Singleton2 instance;
    public static Singleton2 getInstance() {
        if (instance == null) {
            synchronized (Singleton2.class) {
                if (instance == null) {
                    instance = new Singleton2();
                }
            }
        }
        return instance;
    }
}






欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2