1.概述
java是最先支持多线程开发的语言之一,多线程技术使得程序的响应更快,可以再进行其他工作的同时一直处于活动状态。
性能提升的本质就是榨取硬件的剩余价值即就是提高硬件的利用率。
并发编程所带来的问题:
安全性问题(访问共享变量)
性能问题(线程切换所导致的CPU开销)
2.并行与并发
并行:同一时间节点,多个任务同时进行
并发:同一时间结点,多个任务依次交替执行。
对于并发而言,操作系统存在一个任务调度片,线程切换即就是时间片太过短暂,线程切换的速度非常快,因此在微观上看并发是串行的,宏观上来看就是并行的。
举个例子来说:
并行:两个人在两台饮水机在同时执行接水操作
并发:两个人在一台饮水机依次交替接水
3.并发编程的核心问题
核心问题即就是缓存所带来的可见性问题,编译优化所带来的有序性问题以及线程切换所带的原子性问题。
3.1可见性
一个线程对共享变量的修改,另外一个线程立马可以看到,称之为可见性。
在多线程的时代,每个CPU都有自己本地缓存,缓存又只对处理器的内核可见,导致了缓存的内容与内存的内容可能会不一致。处理器采用写缓冲区来保存写入内存的数据,而写缓冲区采用批处理的方式刷新内存,也就是说缓存中的数据并不会立即刷新到内存中,这也就导致了可见性问题。
3.2有序性
程序按照代码的先后顺序之星称之为有序性。
在程序执行的过程中,编译期经常会为了优化程序性能,从而乱序执行,也就导致了有序性问题。CPU的读等待同时指令执行是CPU执行乱序的根源。............................0
3.3原子性
一个线程或多个线程在执行的过程中不被中断的特性称之为原子性。
线程切换导致了原子性问题,具有原子性的量,同一时刻只能有一个线程执行操作。
4.volatile关键字
volatile修饰一个共享变量时,可以保证可见性与有序性,无法保证其原子性
底层实现原理:
使用内存屏障,内存屏障可以限制处理器以及编译器做出的指令重排
volatile所修饰的共享变量编译为汇编语言之后会多出一个lock前缀,通过lock指令前缀+MESI缓存一致性协议可以保证操作的可见性。
有序性实现:主要通过对valitale修饰的变量在进行读写操作前后加入内存屏障来限制指令的重排。
可见性实现:主要通过lock前缀指令+MESI缓存一致性协议实现;对volitale修饰的量进行写操作时,JVM会给CPU发送一个lock指令前缀,CPU在执行完写操作时,会立即将数据刷新到内存中,因为MESI缓存一致性协议,其它线程会对主线进行嗅探即就是与内存值作对比,查看自己的本地缓存是否被修改过,一旦被修改就会过期掉本地缓存,从主线中刷新到最新的内存值,这样就保证了可见性。
5。如何保证原子性?
5.1synchronized关键字
synchronized是一种通用的技术方案,JAVA语言提供的synchronized关键字,就是锁的一种实现,synchronized是一种独占锁、排它锁;并不能改变CPU时间片的切换,但是synchronized锁可以产生阻塞,当一个线程对共享变量进行修改时,其它的线程必须在外面排队等候,因次同一时刻只能有一个线程进行操作。因此synchronized一定可以保证原子性。
5.2原子变量类
javaJUC包中存在两个包,一个是locks包,另一个是atomic包;两者都可以解决原子性问题。
synchronized是一种阻塞式实现原子性
原子变量则是非阻塞式实现原子性
原子变量类的原理:通过volatile关键字+CAS算法实现原子性
5.3CAS算法
CAS(compare and swap)比较并交换,CAS算法是硬件对并发操作的一种支持,是乐观锁的一种实现方式,采用自旋锁的思想,是一种轻量级锁
CAS算法的实现原理:每次判断内存值是否与预估值相同,如果不相同,则证明内存值已经被其他线程修改过了。则需要拿到最新值作为新的预估值,然后再次与内存值作比较;该线程就会不断的判断内存值是否被修改过,这就是自旋的思想。
CAS算法共有三个操作数
预估值:A
内存值:V
更新值:B
当且仅当预估值与内存值相同时,将内存值赋给更新值
CAS算法的优点:当判断不成功时,不会更新值,也不会产生阻塞,继续判断执行,提高了效率。
CAS算法的缺点:不断地自旋,会导致CPU的消耗,高并发的情况下可能会导致CPU跑满。
CAS算法所带来的问题:ABA问题:即就是当一个线程将内存值由A修改为B,另外一个线程将B又修改为了A;预估值与内存值相比较还是相同;CAS算法就无法判断内存值是否被修改过。
解决方案:使用类版本添加号,每次对内存的修改都添加上类版本添加号,判断时先判断内存值是否相同,再判断类版本添加号是否相同,这样就可以避免ABA问题。