【并发编程】并发编程的核心问题

前言

        缓存导致的不可见性问题编译优化带来的乱序性问题线程切换带来的非原子性问题。其实缓存、编译优化、线程切换的目的和我们写并发程序的目的是相同的,都是为了提高程序性能和安全性。但是技术在解决一个问题的同时,必然会带来另外一个问题,所以在采用一项技术的时候,一定要清楚它带来的问题是什么,如何去规避。

不可见性问题

        一个线程对共享变量的修改,另一个线程能够立刻看到。对于如今的多核处理器,每个CPU内核都有自己的缓存,而缓存仅对它所在的处理器内核可见,CPU缓存与内存的数据不容易保证一致。为了避免处理器停顿下来等待向内存写入数据而产生的延迟,处理器使用写缓冲区来临时保存向内存写入的数据。写缓冲区合并对同一内存地址的多次写,并以批处理的方式刷新,也就是说写缓冲区不会即时将数据刷新到主内存中。缓存不能及时刷新导致了不可见性问题。                 

        Java内存模型设计有主内存和工作内存,模型规定了所有的变量都存储在主内存中,每条线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。这里的工作内存是JMM的一个抽象概念,也叫本地内存,其存储了该线程读/写共享变量的副本。就像每个处理器内核拥有私有的高速缓存,JMM中每个线程拥有私有的本地内存。                                             

        总结来说,线程不能直接对主内存中的数据进行操作,必须将主内存中的数据加载到工作内存中,这样在多核CPU下就会产生不可见性,而我们的目标就是要做到可见性。volatile可以解决不可见性,volatile修饰的变量,当它被修改时,值会立即更新到主存,其他线程可以及时在内存中读取到最新值

乱序性问题

        指令在执行过程中,为了优化性能,有时候会改变程序中语句的先后顺序,这样的改变可能会影响程序整个运行的结果。CPU在读等待同时指令执行是CPU乱序执行的根源,读指令时可以同时执行不影响的其他指令。被volatile修饰的变量,会禁止指令重排序,从而保证有序性。

非原子性问题

        原子的意思代表着“不可分”,一个或多个操作在CPU执行的过程中要么执行都成功,要么执行都失败,不可中断,不存在中间状态,我们称之为原子性。原子性是拒绝多线程交叉操作的,不论多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。CPU能保证的原子操作是CPU指令级别的,而不是高级语言的操作符。                                                                       

        线程切换导致了原子性问题,Java并发程序都是基于多线程的,自然也会涉及到任务切换,任务切换的时机大多数是在时间片结束的时候。我们现在基本都使用高级语言编程,高级语言里一条语句往往需要多条CPU指令完成。如 count++,至少需要三条CPU指令。                               

        指令1:首先,需要把变量 count 从内存加载到工作内存;                                                 

        指令2:之后,在工作内存执行 +1 操作;                                                                           

        指令3:最后,将结果写入内存。                                                                                           

        两个线程A和B同时执行 count++,即便 count 使用 volatile 修饰,我们预期的值是2,但实际可能是1。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值