JVM线程:OS线程 == 1:1
<jdk1.2 重量级锁
(个人见解,尤其在重量级锁方面)
重量级锁和轻量级锁的概念
重量级锁:交给 OS 管理锁的争抢,释放 CPU 资源
轻量级锁:JVM 自己管理锁的争抢(无锁,自旋锁),CPU资源不释放
不上锁时的多线程并发问题
在不上锁的情况下,多个线程可能会出现以下的并发问题
轻量级锁的实现方式:CAS
查看现在的值是不是原先读到本地线程的值,是就修改,不是重新读取到本地,继续循环;
因为需要一直循环直到修改成功,所以使用 while 循环,所以轻量级锁也叫自旋锁
CAS可能引起的两个问题
ABA问题
在 CAS 判断的过程,可能出现此时数值为原先的值,但是该数值是经过修改后再修改才变成原先的值(0->1->0),此时 CAS 再修改值得到的结果可能和我们想要的结果不同。
例子
如图原本线程期望修改后的栈中元素为 B -> B ,但是由于中间其他线程修改了栈,现在却变成了 B -> C,与期望不同
解决办法
添加一个版本号 version(添加时间戳),区别前后的值
CAS原子性问题
CAS 叫做 compare ans swap ,本质其实也是如下思想(假设原本读入为 0 ,现在需要改变为 1)
if(i == 0) {//在非原子性的情况下,如果条件为true,进入代码块后,i可能会被其他线程改变,导致得到的不是预期结果
i = 1;
}
所以 CAS 本身的底层实现必须支持原子性;
CAS的底层实现
底层实现是使用了汇编语言的 lock 指令前缀 和 cmpxchg 指令
cmpxchg 指令
作用就是比较并交换操作数,该指令本身不保证原子性;
lock 指令前缀
是给共享内存上锁,保证在执行指令时 CPU 可以独占资源(在硬件层面上上锁)
多核CPU下并发问题
在单核 CPU 时可以不加 lock 指令,因为线程的执行实际要落到 CPU 上的,单核 CPU 的情况下,每一个时刻都只能有一个线程在执行,所以单核下,就是该指令执行时不存在其他线程修改资源的情况;
但在多核 CPU 的环境下,某个时刻下是有可能多个线程同时执行代码的,而 cmpxchg 指令不保证原子性,所以我们需要加入 lock 指令前缀对资源进行上锁,保证只有一个 CPU 可以使用该资源
重量级锁
把线程对锁的争抢交给 OS 管理,即让需要获得的锁资源的线程进入阻塞状态,此时 CPU 不再停留在该线程上(即线程释放 CPU 资源);
OS 维护一个等待队列,将需要争抢该锁资源的线程放入队列等待,锁资源释放后,由 OS 唤醒队列的一个线程,获取资源;
等待和唤醒的函数
wait
使线程进入阻塞
notify
唤醒一个现场
重量级锁和轻量级锁的效率问题
当多个线程在争夺锁资源时,使用轻量级锁会 CPU 会把大部分时间花在线程切换上,导致效率低下;
而大部分情况下都只有一个线程会获取到锁资源的情况下,使用重量级锁交给 OS 管理,代价太高,效率低;
故在竞争激烈的情况下选择重量级锁,竞争不激烈的情况下选择轻量级锁
具体情况具体分析