并发编程
1.并发与并行
并发:在一段时间内做了多件事(宏观上是同时进行,微观上是一个一个执行,只不过时间片交替时间短,看起来像同时执行)。
并行:在一个时间点有多件事情在执行(基于多核cpu,cpu在一个时间点只能做一件事情)
2.java的内存模型
由于cpu和内存之间读写速度有很大差异,所以在cpu和内存之间加入了高速缓存
![](https://img-blog.csdnimg.cn/img_convert/d4d678c34dbc710b68f785426080021e.png)
3.并发编程后的三大问题以及解决方案
1.可见性
由于cpu,工作缓存、主内存之间的速度差异,导致线程1在工作中的数据先读入工作内存
(前提多核)这时如果有另一个线程2也在修改上一个线程工作区中有的数据,而这时,线程
1拿不到修改后的数据,导致计算结果的偏差。
主要问题:工作内存中的数据互相不可见
使用volatile 关键字可以解决
2.原子性
由于cpu需要提高效率,cpu会切换线程,提高cpu的使用效率,而cpu所能保证的指令时cpu
指令的原子性,而不是java指令的原子性。
例如 count++操作
cpu中:拿到count的值
运算count=count+1
赋值
如果当线程1拿到count值时线程进行了切换,线程二也拿到了count没操作的值,纵然是保证了
可见性,最终的结果也会出现偏差。
保证java指令的原子性(加锁)
3.有序性
在编程过程中,程序为了提高性能会进行重排序,如果数据之间没有数据依赖则,程序会对其进
行重排序
例如:
Boolean inited=false;
init();
public void init(){
while(inited)
work() ;
}
看起来inited和init()并无关系但是实际会影响,init()里用到了inited变量
使用volatile 关键字可以解决
总结:由于cpu,缓存,内存读取速度差异产生可见性问题,由于线程切换产生原子性问题,由于编译优
化产生有序性问题。
4.解决问题
1.volatile(有序性,可见性)
一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile修饰
1.一个线程修改了被volatile修饰的共享变量或方法则,在线程运行中对另一个线程可见
2.一个线程修改了被volatile修饰的共享变量或方法则,在运行过程中不允许cpu改变顺序
3.不能改变变量操作的原子性问题
2.原子性
1.加锁
①加synchronized(可以在方法,代码块,变量)
@利用JUC原子变量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VkCQJev6-1626864358490)(C:\Users\57156\AppData\Roaming\Typora\typora-user-images\image-20210721100852745.png)]
原子变量的实现底层是 volative+cas
3.CAS
**CAS;**是一种乐观锁是实现方式,采用自旋锁的思想,是一种轻量型机制。
cas的机制
三个变量
内存值 V
预估值(工作区拿到的值)A
更新值(更新后的值)B
流程
1.在对预估值进行操作前一直循环判断预估值与内存值是否相等
2.如果相等则执行更新操作,更新值赋值给内存值
3.如果不相等,获取内存值赋给预估值,获得cpu继续执行,对该操作进行重新运算
**优点:**执行效率高
**缺点:**不断对于内存的值和预估值进行不停的判断,不断切换线程导致
cpu的消耗过大,在低并发时是个不错的选择
ABA问题:
当一个线程更改了变量的值又改了回去,另一个线程进来时查询内存
的值发现相等直接进行操作。误以为值没被修改。
比如:银行内部员工,从系统挪走一百万,之后还了回来,系统感知不到
岂不是要出事。
解决方案:
在变量的后面加上版本(A,1)(A,2),只需要获取最新的版本就会
发现值有没有被更改。
ConcurrentHashMap
Hash表:由数组加链表构成
HashMap:线程不安全
HashTable:线程安全但是HashTable是在方法上加入Synchronized(互斥锁)效率较低。
锁的粒度过大,低并发可用
ConcurrentHashMap:采用CAS+synchronized锁的方式,在链表的节点加锁,提高了效率。
(如果并发hash值不同,可以同时插入)
1.通过put元素时,先HashCode()算出hash值,
2.如果是链表第一个值,调用cas原则(判断是否是第一个元素)直接插入,无需枷锁
3.如果不是则给此链表第一个元素加锁,并插入元素
4.插入完成释放锁