并发原子性问题

1、原子性问题的产生

1、下面的count存在jvm运行数据区中的哪个位置?

public static int count=10000;

在jdk1.6中存在方法区中,jdk1.7以后存在堆中

2、根本原因

public static int count=10000;
public void main(int [] args){
count++;
}

count++在cpu指令级别中会发生如下操作:

拷贝堆中的共享变量count到虚拟机栈中
在操作数栈中进行增加操作
写回共享变量中

故:根本原因是指令的原子性无法满足高级语言的原子性

3、为什么要拷贝共享变量到虚拟机栈中

两种指令集架构:
1、栈式:基于操作数栈进行操作,此时并没有数据地址,因为数据存在栈中,好处是简单的指令操作,可以具有更好的移植性
2、寄存器:多地址指令,携带了数据和数据地址,少量数据可进行复杂计算
java虚拟机是栈式的指令架构,具有更好的软件移植性

2、如何解决原子性问题

2.1、利用锁的互斥性将并发场景下的非原子数据操作强制串行

2.2、锁的分类

  • 悲观锁:数据库行锁/表锁(LBCC机制),java关键字synchronized

  • 乐观锁:Mysql的MVCC,JUC的原子性操作,redis中的watch/exec

3、乐观锁实现:JUC原子操作类(AtomicXXX)

实现机制:CAS(compare and swap)操作+自旋锁
1、CAS需要三个操作数,分别是内存地址(在Java内存模型中可以简单理解为主内存中变量的内存地址)、旧值(在Java内存模型中,可以理解工作内存中缓存的主内存的变量的值)和新值。CAS操作执行时,当且仅当主内存对应的值等于旧值时,处理器用新值去更新旧值。
2、自旋锁:让当前线程不停地的在循环体内执行的方式实现。这就叫做自旋,do-while循环

AtomicXXX
CAS(compare and swap)+自旋锁 预期值 目标值 替换值
CAS操作是自旋的一个操作,进行一个原子的更替。自旋的作用可以让线程等待,可以说是一种锁机制

3、悲观锁实现:synchronized原理

3.1、用法

public synchronized void method(){}  //锁定的资源--->this

public static synchronized void method2(){}  //锁定的资源--->锁定的是class对象

public void method(){
 synchronized (obj){  //锁定对象,直到执行完整个同步方法体
 拿到obj锁资源的线程就可以执行这里的代码
 }

}  

3.2、对象的组成:

  • 对象头:
  • Mark Word:一系列的标记位
  • classs point:指向对象对应的类数据的内存地址,反射中getclass能获得对象类型是因为对象头有指向方法区的classpoint字段,
  • 实例数据就是存放数据
  • 填充数据是为了满足对象大小是8字节的倍数,与cpu的计算有关
    在这里插入图片描述

3.3、synchronized锁升级过程

偏向锁:只有一个线程去访问,并不存在多线程争用,此时是不需要触发同步的。markword里的线程id会记录第一次来的线程id,当同一线程下一次来时就检查markword的线程id是否和现在来访问的线程一致,若一致直接放行。无争抢

轻量级锁:当偏向锁失去作用时,就会升级为轻量级锁。失去作用即第一个线程没有释放锁或前偏向线程不再存活,但又未设置可重新偏向的设置。有争抢,不激烈

争夺过程如下:争夺的线程在栈中开辟“lock record”的空间,通过cas操作将markword锁的标志改为00,将markword中的数据copy至“lock record”空间中。争夺的线程谁的cas操作成功即获得该对象的轻量锁,此时markword指向栈中锁记录的指针指向栈中“lock record"地址,未争夺成功的线程,采用自适应自旋(cas)的方式进行重试等待(并没有切换线程状态)。
CAS中mark word中轻量级pointer的预期原值是什么,是null,而目的值则是自己,要将那个mark word的pointer修改为指向自己,而当前原值,也就是只有在pointer是null的时候也就是没有线程获取对象锁。才能够使CAS操作成功,达到一定的cas操作没有成功换为重量级锁

重量级锁: 线程状态的切换会带来性能的消耗。当线程采用自适应自旋的方式无法拿到锁,会通知jvm将对象头里的锁升级为重量级锁。未获得对象锁的线程直接进入阻塞状态,此时不在消耗cpu资源,等拥有锁的线程释放锁后,唤醒阻塞的线程,再次竞争锁。此时markword的30位将指向该加锁的objecmonitor对象,任何一个用synchonized关键字修饰的对象都会生成一个与之相随相伴的objecmonitor对象
线程的调度是在内核态运行的,而线程中的代码是在用户态运行,会带来用户态与内核态的切换

objecmonitor对象
在这里插入图片描述

升级过程
在这里插入图片描述

3.4、synchronized与lock的区别

lock不会升级,sychnonized会升级(jdk1.6后)

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页