多线程五 synchronized实现原理及优化
发布日期:2022-04-11 08:52:50 浏览次数:6 分类:技术文章

本文共 3259 字,大约阅读时间需要 10 分钟。

synchronized实现原理及优化

1. synchronized实现原理

sychronized的使用场景:

在这里插入图片描述

对象锁monitor机制

1. synchronized修饰同步代码块

先来看一段简单的代码

在这里插入图片描述

为了解到synchronized的底层实现原理,我们来对这段代码进行反编译

先通过cd命令进入到src下的package目录

先编译javac Test.java,生成class文件

反编译命令:

javap -c -v Test.class

然后我们来看生成的字节码:

在这里插入图片描述

执行同步代码块后首先要先执行monitorenter指令,退出的时候执行monitorexit指令。通过分析之后可以看出,使用synchronized进行同步,其关键就是要获取对象的监视器monitor,当线程获取monitor后才能继续往下执行,否则就只能等待。

而这个获取的过程是互斥的,即同一时刻只有一个线程能够获取到monitor

上述字节码中包含一个monitorenter指令以及两个monitorexit指令。这是因为JVM需要确保获得的锁在正常执行路径、异常执行路径上都能够被解锁。

2. synchronized修饰同步方法

在这里插入图片描述
当用synchronized标记方法时,字节码中方法的访问标记包括 ACC_SYNCHRONIZED

  • 进入该方法时,JVM需要进行 monitorenter 操作
  • 退出该方法时,不管是正常返回,还是向调用者抛异常,JVM均需要进行 monitorexit 操作

monitorenter—monitorexit

  • 关于monitorenter 和 monitorexit 的作用,我们可以抽象地理解为:每个锁对象拥有一个锁计数器和一个 “指向持有该锁的线程” 的指针

  • 当执行monitorenter时,如果目标锁对象的计数器为0,那么说明它没有被其他线程所持有。在这个情况下JVM会将该锁对象的持有线程设置为当前线程,并且将其计数器加1

  • 在目标锁对象的计数器不为0的情况下,如果锁对象的持有线程是当前线程,那么JVM可以将其计数器再加1;否则需要等待,直至持有线程释放该锁

  • 当执行monitorexit时,JVM则需将锁对象的计数器减1。当计数器减为0时,便代表该锁已经被释放掉了


可重入锁

当执行monitorenter时,对象的monitor计数器值不为0,但是持有锁的线程恰好是当前线程。此时将monitor计数器值再次+1,当前线程再次进入同步方法或代码块

之所以采用这种计数器的方式,是为了允许同一个线程重复获取同一把锁

【证明锁的可重入与互斥】

synchronized修饰的test1方法中调用test2方法

class Sync implements Runnable {
@Override public void run() {
test1(); test2(); } public synchronized void test1() {
if (Thread.currentThread().getName().equals("A")) {
test2(); } } public synchronized void test2() {
if (Thread.currentThread().getName().equals("B")) {
System.out.println("B线程进入该同步方法test2()..."); }else {
//此时B线程还没有启动 System.out.println(Thread.currentThread().getName() + "线程--->进入test2()方法"); } }}public class Reentrant {
public static void main(String[] args) throws InterruptedException {
Sync run = new Sync(); new Thread(run,"A").start(); //有时间差,保证A先启动 Thread.sleep(2000); new Thread(run,"B").start(); }}

在这里插入图片描述

如果一个类中拥有多个synchronized方法,那么这些方法之间的相互调用,不管是直接的还是间接的,都会涉及对同一把锁的重复加锁操作


2. JDK1.6后对synchronized的优化

synchronized的操作都是互斥的,效率低。当一个线程拿到了锁资源之后,因为要保证同步,所以其他线程只能等待该线程释放锁,效率自然降低。

优化的思想:让每个线程通过同步代码快时的速度提高


CAS自旋

CAS自旋,无锁实现的同步----乐观锁Compare And Swap

在这里插入图片描述

Compare And Swap(O, V, N)

  • O:当前线程存储的变量值

  • V:内存中该变量的具体值

  • N:希望修改后的变量值

线程1先进入同步代码块:

  1. O ----> i = 0

  2. V ----> i = 0

  3. O == V

  4. N ----> i = 10

线程2再进入同步代码块:

  1. O ----> i = 0

  2. V ----> i = 10

  3. O != V

  4. N ----> 修改失败

  • 当 O == V 时,此时表示还没有线程修改共享变量的值,此时可以成功的将内存中的值修改为N

  • 当 O != V 时,表示此时内存中的共享变量值已经被其他线程修改,此时返回内存中的最新值V,再此尝试修改

自旋问题可以理解为开车过红绿灯:

  • 线程挂起阻塞:车熄火

  • 自旋: 脚踩刹车,车不熄火

自旋带来的问题: ABA问题

  • 解决方法: 添加版本号 A.1----> B.2

自旋在CPU上跑无用指令,虽然减少了操作系统的开销,但是浪费了CPU资源

JVM自适应自旋 : 根据以往自旋等待时能否获取锁,来动态调整自旋的时间(循环数)

  • JVM尝试自旋一段时间,若在此时间内线程成功获取到锁,在下次获取锁时,适当延长自旋时间;
  • 若在此时间内线程没有获取到锁,在下次获取锁时,适当缩短自旋时间

公平性问题

  • 处于阻塞状态的线程可能一直无法获取到锁,自旋状态的线程更容易获取到锁

  • Lock锁可以实现公平性,synchronized无法实现公平锁


偏向锁

JDK 1.6 之后默认synchronized

最乐观的锁:进入同步块或同步方法始终是一个线程

在不同时刻时,当出现另一个线程也尝试获取锁,偏向锁会升级为轻量级锁


轻量级锁

  • 不同时刻有不同的线程获取锁,“亮黄灯策略”,基本不存在锁竞争
  • 同一时刻,如果不同线程尝试获取锁,会将偏向锁自动升级为重量级锁

重量级锁

  • JDK 1.6 之前的锁都是重量级锁,将线程阻塞挂起

  • 锁只有升级过程,没有降级


锁粗化

将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成为一个范围更大的锁

比如使用StringBuffer中的apperd方法来添加字符串

sb.append("a");sb.append("b");sb.append("c");

这里每次调用append方法都需要加锁和解锁操作

如果虚拟机检测到有一系列连串对同一个对象加锁和解锁操作,就会将其合并成一次范围更大的加锁和解锁操作,即在第一次append方法时进行加锁,最后一次append方法结束后进行解锁


锁消除

当对象不属于共享资源时,对象内部的同步方法或同步代码块的锁会被自动解除

转载地址:https://iqqcode.blog.csdn.net/article/details/97502529 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:多线程交叉打印数字(1-75)
下一篇:多线程二(线程通信)

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月02日 13时16分30秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

上手Pandas,带你玩转数据(1)-- 实例详解pandas数据结构 2019-04-27
上手Pandas,带你玩转数据(2)-- 使用pandas从多种文件中读取数据 2019-04-27
上手Pandas,带你玩转数据(3)-- pandas数据存入文件 2019-04-27
爬虫遇上不让右击、不让F12的网站,该怎么办? 2019-04-27
上手Pandas,带你玩转数据(4)-- 数据清洗 2019-04-27
上手Pandas,带你玩转数据(5)-- 数据转换与数据定位 2019-04-27
上手Pandas,带你玩转数据(6)-- 摆脱对pandas可视化丑图的刻板印象吧 2019-04-27
从零开始,学会Python爬虫不再难!!! -- (1)开篇:初识爬虫,基础铺垫 丨蓄力计划 2019-04-27
从零开始,学会Python爬虫不再难!!! -- (2)承接:解析网页,抓取标签 丨蓄力计划 2019-04-27
AttributeError: module ‘urllib‘ has no attribute ‘quote‘的解决办法 2019-04-27
linux shell — 6.初识 EXT2 文件系统 2019-04-27
Java — String(字符串) 2019-04-27
linux shell — 7.linux 磁盘与文件系统管理 2019-04-27
linux shell — 8.linux 磁盘与文件系统管理(2) 2019-04-27
Java — 事件监听、事件处理 初体验 2019-04-27
linux — Centos 7(第一天) 使用时出现的问题及解决方法 2019-04-27
数据结构 — 图的概述 2019-04-27
Centos 7 上 Eclipse 无法输入中文解决方法 2019-04-27
数据结构 — 图之邻接表存储创建和深度优先遍历 2019-04-27
Centos 7 — Gedit 配色方案 2019-04-27