美文网首页分布式架构
【并发编程是个什么鬼】并发编程Bug的源头

【并发编程是个什么鬼】并发编程Bug的源头

作者: 夏天的风风风 | 来源:发表于2019-04-29 19:46 被阅读0次

            【今天重温了大神写的并发相关文章】

            概念定义

            可见性:一个线程对共享变量的修改,另外一个线程能够立刻看见,我们称之为可见性。

            任务切换、时间片:操作系统允许某个线程执行一小段时间,例如50ms,过了50ms操作系统就会重新选择一个进程来执行,这个过程叫做“任务切换”,其中50ms就叫做时间片。

            原子性:一个或者多个操作在CPU执行的过程中不被中断的特性称为原子性。

            为什么并发程序容易出问题呢?

            1  缓存导致的可见性问题

            在单核系统中,所有的线程操作的都是同一个CPU的缓存,一个线程对缓存的读写,另一个线程一定是可见的。假如有共享变量A,如果线程一改变了它的值,那么线程二再访问A的时候,得到的一定是被线程一修改过的A的最新值,这个过程就是可见性。

            在多核系统中,多个线程在不同的CPU上运行,这些线程操作的是各自所在CPU的缓存,借用上面的场景来说明,如果CPU1的线程改变了共享变量A的值,那么CPU2的线程是感知不到的,所以不同CPU的线程在操作共享变量时就不具备可见性,这是目前硬件的客观条件造成的,算是个“硬件坑”。

            

    单核CPU缓存与内存的关系 多核 CPU缓存与内存的关系

            2  线程切换带来的原子性问题

            Java并发程序都是基于多线程的,自然会涉及到任务切换。任务切换的时机大多数是在时间片结束的时候,针对高级语言来说,一条命令往往会对应多条CPU指令

            操作系统做任务切换的时机,可以是任何一条cpu指令执行完之后。所以CPU能保证的原子操作是cpu指令级别的,不是高级语言级别,所以高级语言层面的原子性需要专门处理,很多朋友会混淆这里。

    线程切换示意图

            

            3  编译优化带来的有序性问题

            有序性导致的问题在Java中有一个经典案例,即单例模式的双重检查锁实现方式。

            我们先来看段代码:

    双重检查锁单例

            双重检查锁看上去很完美,但实际上getInstance方法并不完美,问题出在new操作上,我们以为的new操作应该是如下顺序:

            1  分配一块内存M;

            2  在内存M上初始化Singleton对象;

            3  将M的地址赋值给instance变量。

            但是实际上优化后的执行路径是这样的:

            1  分配一块内存M;

            2  将M的地址赋值给instance变量;

            3  在内存M上初始化Singleton对象。

            优化后会导致这样一个问题,假设线程A先执行getInstance方法,完成指令2之后发生了线程切换现象,切换到了线程B上,线程B也在执行getInstance方法,结果发现instance已经有了,就直接返回instance来使用,但实际上instance在A线程中没有真正的做到初始化,因为没完成第三步,所以线程B在使用instance的时候一定会发生空指针异常。整个过程如下:

    相关文章

      网友评论

        本文标题:【并发编程是个什么鬼】并发编程Bug的源头

        本文链接:https://www.haomeiwen.com/subject/ruilnqtx.html