从java内存模型到并发安全
![]()
![]()
![]()
![]()
![]()
![]()
计算机内存模型
主内存 (运行内存) + CPU(高速缓冲区1+高速缓冲区2+高速缓冲区3+cpu处理器【寄存器】计算)
由于CPu运行效率极高,导致内存读写存在较大差距于是就有了下面运行模型:
模型走势:从内存加载数据到高度缓冲区3,高速缓冲区3-->高速缓冲区2-->高速缓冲区1-->cpu计算-->结果写回高度缓冲1-->写回高速缓冲区2-->写回高速缓冲区3-->写回主内存
java内存模型
java内存模型的作用:规范了java的虚拟机和java内存是如何协同工作的,规定了一个线程是如何和何时可以看到其它线程修改过的共享变量的值,以及在必须时如何同步访问共享变量;
Head(堆):运行时的数据区,由垃圾回负责回收,动态分配内存大小(缺点:存储速度相对较慢)
Stack(栈):存取速记比堆要快,仅次于计算机的寄存器,数据可以共享,只能存储基本类型变量和对象句柄
主内存:主内存中读取,工作内存中修改
java的工作内存==》{
CPU的高速缓存
CPU的寄存器
}
这里的工作内存即是:每个线程的私有本地内存,存储的是该线程读或写的一个副本
从主内存中读取加载到工作内存中,在执行引擎中使用后赋值到工作内存的变量,工作内存变量传输到主内存并写;
程序的无序性:
源代码==>编译器(重排序优化)==>指令级(并行重排序)==>内存(系统重排序)==>执行(指令重排序)
java内存模型--同步八种操作
lock(锁定):作用于主内存的变量,吧一个变量标识为一天线程独占的状态
unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以别其他线程锁定
read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,一边随后的load动作使用
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放到工作内存的变量副本中
use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量
store(存储):作用于工作内存的变量,把工作内存的一个变量的值传送到主内存中,一边随后的writte的操作
witte(写入):作用于主内存的变量,它把store的操作从工作内存中的一个变量的值传送到主内存变量中
并发安全问题引发原因:
定义:多个线程并发操作该类,导致该类无法表现出正确的行为;
原因:Java中例如有A,B两个线程,
A访问资源C int = 1,资源加载到内存,资源在加载到A线程的栈内存中,在A的栈内存中计算++后返回到内存中;
同时B线程读取资源C int = 1,此时B访问到的资源时是A为修改的资源,C资源被加载到栈内存中计算++后,放回到内存;
所以A、B线程并发进行,导致AB同时读取资源C,由于现在是资源不可见,所以A是1++,B也是1++,所以结果仍然是2;
保证原子性和可见性即可实现线程安全;
实现线程安全:{
1.无状态类
即该类无成员变量属性,只有方法;(类状态即指类的成员变量)
2.类不可变
即该类成员变量都定义为final,不可修改
3.volatile
最轻量级的同步机制,并法能够保证线程安全,只能保证可见性:
用在:数据不存在依赖,即当前计算不依赖上一个值(比如:count++);一个线程写,其他线程只读
4.CAS(CompareAndSet)
比较并且交换
5.ThreadLocal
每一个使用该变量的线程内部都复制副本然后进行操作,可以在内存当成是一个map存储,比如有ThreadLocal<String>变量 ,现在有一个线程A调用get方法获取string值,此时在ThreadLocal内部相当于定义一个Map,key是该线程ID,Value是String,通过get和set获取和赋值;
6.加锁synchronized关键字
比如:对a进行加锁机制
static Object a = new Object();
Synocronzied(a){
/TODO
}
使用锁时候可能导致的问题:
简单死锁:
产生条件:多个线程争夺多个资源,比如A、B线程操作C、D资源,A线程获取C的锁后获取D的锁,然后进行操作,线程B获取D的锁后获取C的锁然后进行操作,可能存在线程A、B同时进行,A获取到了C的锁,当获取D的锁时候发现D的锁被B获取;此时B已经获取D的锁,但是由于C的锁被A获取,所以A、B线程互相等到导致都无法获取锁出现死锁
踩过的坑:
当时由于业务要求有A,B,C三Service,其中A的Service是执行需要锁定资源1和资源2,ServiceB执行需要锁定资源2和资源3,ServiceC执行需要锁定资源3和4,项目上线后,开始的时候可以正常的运行,但是一段时间后该页面无法响应,程序没有任何报错和异常,很长时间排查难以想到问题原因;
解决思路:按照一定顺序锁定资源
动态死锁
业务举例:现有转账系统,银行要求,对于转账用户需要锁定双方用户后进行转账,即转账是A(from)-->B(to),锁定from账户后锁定to账户,但是现在又A-->B,同时B-->A,就可能出现动态死锁;
解决思路一:
可以根据用户的ID用java的identityHashCode生成唯一的hash值,比较该hash值,如果form>to,则先锁from,在锁to,如果from<to,就先锁to,在锁from,但是由于Hash可能存在hash碰撞,即是from的hash==to的hash,虽然概率是千万分之一,但是可能会出现,此时即当from的hash==to的hash时候,定义常量,先锁定该常量后在去锁定A或者B;
在上图中不全最后的else,并定义常量,加锁该常量后在去加锁to和from;
解决思路二:
用显示锁:在循环中尝试去拿到该类的显示锁(需要在类中定义显示锁)使用tryLock,如果拿到执行,如果没有拿到则释放,切记:在final中一定要释放该锁,为了避免活锁(即显示锁导致,比如A线程获取C的锁后获取D的锁,但是此时D的锁被B获取,同时B获取了D的锁后去获取A的锁,当发现都无法获取时候都会释放锁后A又获取C的锁同时B又获取D的锁,当A获取D的锁被B获取,B获取C的锁被A获取,又会同时释放...依次执行即叫活锁),解决思路,使用random使得线程睡几毫秒既可以解决