Java并发机制底层实现原理是什么

发布时间:2021-07-01 12:17:01 作者:chen
来源:亿速云 阅读:196

这篇文章主要讲解了“Java并发机制底层实现原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java并发机制底层实现原理是什么”吧!

Java并发机制的底层实现原理

术语了解
缓存行:缓存的最小操作单位。
原子操作:不可被中断的一个或一系列操作。
CAS(Compare and Swap),比较并设置。用于在硬件层面上提供原子性操作。CAS操作需要输入二个数值,一个旧值(进行操作前的值)和一个新值,在操作期间比较旧值是否发生变化,没有发生变才替换为新值,发生了变化则不进行交换。

1. volatile

volatile是轻量级的synchronized,在多处理器开发中它保证了变量的可见性.即当一个线程修改共享变量时,另外一个线程能读到这个修改的值.
如果一个字段被声明为volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的.
volatile具有可见性,有序性,不具有原子性

1.1 volatile在CPU中的实现原理

//Java代码如下
instance = new Sington();  //instance 是volatile变量
//转换为汇编代码如下:
0x01a3deld: movb $0x0,ox1104800(%esi);0x01a3de24: lock add1 $0x0,(%esp);

有volatile变量修饰的共享变量进行写操作时会多出第二行汇编代码,查阅IA-32架构软件开发者手册会发现,Lock前缀指令在多核处理器下会引发二件事情:

  1. 将当前处理器缓存行的数据写回到系统内存.

  2. 这个写回内存的操作会使在其他CPU里缓存该内存地址的数据无效.

为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

1.2 volatile的实现原则

由1.1小节可知,volatile的实现规则即要达到上面的效果,具体二条规则为:

2. synchronized

synchronized具有有序性,可见性,原子性
synchronized实现同步的基础---Java中的每一个对象都可以作为锁,具体表现为:

2.1 Java对象头(synchronized的锁存放位置)

synchronized的锁是存放在Java对象头里面的.准确的说是对象头里面的Mark Word里面.
如果对象是非数组类型,则用二个字宽存储对象头;如果对象是数组内型,则虚拟机用三个字宽存储对象头.(Java对象为一个数组时对象头还必须有一块用于记录数组长度的数据。因为Java数组元数据中没有数组大小的记录)
Java对象头里面有:

2.2 锁的升级与对比

锁一共有四中状态,从低到高为:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态.这些状态会随着竞争而升级,但是锁只能升级而不能降级.

2.2.1 偏向锁

大多数情况锁不仅不存在多线程竞争,而且大多由同一线程多次获取,为了降低获取锁的成本引入了偏向锁.
偏向锁是一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁的时候,持有偏向锁的线程才会释放锁。

以上为偏向锁的特点.

  1. 偏向锁的获取
    检查对象头的Mark Word里是否存储了当前线程的线程ID.是则表示线程已经获得了锁;不是则检查Mark Word中的偏向锁标识是否被设置为1.
    若设置为1(表示当前锁状态是偏向锁),则使用CAS将对象头的偏向锁指向当前线程.
    若没有设置为1(表示当前锁状态不是偏向锁),则使用CAS竞争锁;

  2. 偏向锁的撤销
    首先暂停持有偏向锁的线程,并判断该线程是否活着,若线程不处于活动状态,则将对象头了的Mark Word设置为无锁状态.
    若线程仍然活着,则将持有偏向锁的栈中的锁记录及对象头的Mark Word,要么偏向其它线程,要么恢复到无锁状态,要么标记对象不适合作为偏向锁,最后唤醒暂停的线程。

Java并发机制底层实现原理是什么

  1. 关闭偏向锁
    通过JVM参数 -XX:BiasedLockingStartupDelay=0 来关闭延迟启动.
    通过JVM参数 -XX:-UseBiasedLocking=false关闭偏向锁,关闭后默认进入轻量级锁状态.

2.2.2 轻量级锁

  1. 轻量锁加锁
    线程在执行同步块前,JVM先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头的Mark Word复制到锁记录中.
    线程尝试将对象头的Mark Word替换为指向栈中锁记录的指针,若成功,当前线程获得锁.
    若失败,表示其它线程在竞争锁,当前线程便尝试使用自旋来获取锁.

  2. 轻量锁解锁
    使用原子的CAS操作将当前线程栈帧中的锁记录存储的Mark Word替换回到对象头.
    若成功,表示没有线程在竞争锁.
    若失败,表示当前锁存在竞争,锁便会膨胀为重量级锁.Mark Word中的锁状态变为指向重量级锁的指针.

Java并发机制底层实现原理是什么

因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞了),锁一旦升级为重量级锁便不会恢复.当锁处于重量级锁状态时,其它线程试图访问时度会被阻塞.

2.2.3 锁的优缺点对比

优点缺点适用场景
偏向锁加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。如果线程间存在锁竞争,会带来额外的锁撤销的消耗。适用于只有一个线程访问同步块场景。
轻量级锁竞争的线程不会阻塞,提高了程序的响应速度。如果始终得不到锁竞争的线程使用自旋会消耗CPU追求响应时间。同步块执行速度非常快。
重量级锁线程竞争不使用自旋,不会消耗CPU。线程阻塞,响应时间缓慢。追求吞吐量。同步块执行速度较长。

3. 原子操作

3.1 处理器如何实现原子操作

(1)使用缓存锁定来保持原子性

(2)使用总线锁保持原子性

总线锁就是使用处理器提供的一个LOCK #信号,当一个处理器在总线上输出此信号时,其它处理器的请求将会被阻塞住,那么该处理器可以独占共享内存。但是在锁定期间其它处理器不能操作其它内存地址的数据,所以总线锁定开销较大。

3.2 Java如何实现原子操作

(1)使用循环CAS实现原子操作

使用CAS实现的线程安全的计数器代码。CAS实现原子操作的问题:

  1. ABA问题
    CAS是根据旧值有没有发生变化来更新新值的,所以为了解决ABA问题,可以在变量前加入版本号A->B->A就变成了1A->2B->3A.

  2. 循环时长开销大

  3. 只能保持一个共享变量的原子操作
    jdk1.5后开始,提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里面进行CAS操作。

(2)使用锁机制实现原子操作

JVM内部实现了很多种锁机制,偏向锁,互斥锁,轻量级锁。

感谢各位的阅读,以上就是“Java并发机制底层实现原理是什么”的内容了,经过本文的学习后,相信大家对Java并发机制底层实现原理是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

推荐阅读:
  1. mysql的索引底层之实现原理是什么
  2. ArrayList和LinkedList底层实现原理是什么

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java

上一篇:如何使用node.js爬取知乎图片

下一篇:如何使用PHP判断是手机移动端还是PC端访问

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》