从单例的双重检查锁想到的

  常说的单例有懒汉跟饿汉两种写法。饿汉因为类加载的时候就建立了对象,所以不存在并发拿到不一样对象的问题,但会因为开始就加载了对象,可能会形成一些启动缓慢等性能问题;而懒汉虽然避免了这个问题,但普通的写法会在高并发环境下建立多个对象,单纯加synchronize又会明显下降并发效率,较好的两种写法是静态内部类跟双重检查锁两种。
  双重检查锁这个,你们都很熟悉了,上代码:
public class SingleTest {
    private static SingleTest singleTest;
    //获取单例的方法
    public static SingleTest getInstance() {
        if(singleTest == null){
            synchronized (SingleTest.class){
                if(singleTest == null){
                    singleTest = new SingleTest();
                }
            }
        }
        return singleTest;
    }
}
  这个完美了么,并无。实际是,在多线程环境下以上写法有时会报错。缘由是java的线程内重排序致使的,正确作法应该在singleTest上添加volatile进行修饰。
  咱们知道volatile能够保证可见性,synchronized能够保证可见性一致性跟原子性。既然synchronized比volatile还要强大,为何还要volatile进行修饰呢?
  先来分析错误缘由:AB两个线程同时获取单例,A先进入同步块,进行new操做,但这个new实际要分为3步进行:
    memory = allocate(); // 一、分配对象内存空间
    ctorInstance(memory); // 二、初始化对象
    instance = memory; // 三、设置instance指向刚分配的内存地址
  其中2,3是能够重排序的。若是发生了重排序,这时候虽然synchronized并无执行结束,但若是已经执行了3,则B线程在执行第5行时,可能会读到singleTest的值已经不是null,而此时并无执行2,若是B线程直接将数据返回出去使用,是会有问题的。
  注意:这里是可能会有这种状况,并不必定每次都会发生。《java并发编程的艺术》书中有对此部分的解释。记得在第二次看的时候发现了一个疑问,而且跟一群人讨论了很久:synchronized是基于monitor机制的,在monitorexit的时候回强制刷新线程内存到主存,这也是synchronized可见性的保证;但这个地方,A线程尚未执行monitorexit,为何B线程就读到了还没彻底赋值的singleTest呢,这里是否是有什么问题,说好的原子性呢,不是说在synchronized结束前其它线程没法访问么?
  先解释问题:这个synchronized的可见性,上面的理解没啥问题。但jvm发展至今,实际已经针对如今的硬件作了很大程度优化,基本上很大程度的保障了工做内存跟主内存的及时同步,至关于默认使用了一个不太靠谱的volatile。也就是有monitorexit固然会刷新会主存,但没有到monitorexit的时候,其实也是会刷会主存的,这就解释了这里多线程的时候会出问题的缘由。解决的话,加上volatile,禁止了重排序,等读到有值的时候已经初始化完了,固然也就不会有问题了。
  最后再来看一下:synchronized的这个原子性,网上的解释是这么说的:
   【众所周知,原子是构成物质的基本单位(固然电子等暂且不论),因此原子的意思表明着——“不可分”;由不可分性可知,原子性是拒绝多线程操做的(只有分解为多步操做,多个线程才能对其操做:就像一个盒子里有多个兵乓球,多我的可以从盒子里拿乒乓球;若是盒子只有一个兵乓球,一我的拿的话,其余人就拿不到了;这就是原子性,乒乓球就具备原子性,人就至关于线程)
   简而言之——不被线程调度器中断的操做,如:赋值或者return。好比"a = 1;"和 "return a;"这样的操做都具备原子性。
  原子性不管是多核仍是单核,具备原子性的量,同一时刻只能有一个线程来对它进行操做!】
  这里强调的是同一时间段只有一个线程在执行,
  再回想一下数据库事务的原子性,进行两个操做,若是一个失败了那么另外一个也会回滚;跟synchronized的原子性说的不太同样吧,synchronized的原子性只是同一时间只让一个线程访问而已,若是里边修改了某个公共变量,因为jvm不定时刷新致使的可见性问题,在synchronized尚未执行完的时候,其它线程也是大几率能够看到这个改动的。这是jvm决定的,synchronized的原子性“管不到这里”。(jvm怎么决定的,有啥依据~~这个,水平有限,没搞清楚,就只能先赖给jvm了)
  再说一点,数据库的原子性咱们是很熟悉的,但是它是执行结束了,其它事务才会看到的么?显然不是,有事务的隔离级别么,若是把隔离级别降到最低(读未提交),A事务一修改,还没提交,B事务就能看见啦。跟这里的原子性还有可见性对比一下,就是java的这个可见性至关于数据库的最低级别了。说到数据库事务的原子性,真的必定能保证要么所有执行,要么回滚么?这个固然不能,假设一个事务很是庞大,执行了一半,断电了~~数据库在设计上固然会考虑这个,重启数据库后会根据日志来继续执行或者回滚,但若是日志跟数据库的数据对不上怎么办,数据库本身搞不定了,这时候固然就须要专业dba出手了。那java的原子性呢,执行到一半,异常了,,,程序员本身考虑异常处理,执行到一半,断电了,,,,没得办,来电重启呗,相关业务数据处理,程序员本身想办法搞定(通常设计上应该会有相关处理)。
相关文章
相关标签/搜索