Blage's Coding Blage's Coding
Home
算法
  • 手写Spring
  • SSM
  • SpringBoot
  • JavaWeb
  • JAVA基础
  • 容器
  • Netty

    • IO模型
    • Netty初级
    • Netty原理
  • JVM
  • JUC
  • Redis基础
  • 源码分析
  • 实战应用
  • 单机缓存
  • MySQL

    • 基础部分
    • 实战与处理方案
    • 面试
  • ORM框架

    • Mybatis
    • Mybatis_Plus
  • SpringCloudAlibaba
  • MQ消息队列
  • Nginx
  • Elasticsearch
  • Gateway
  • Xxl-job
  • Feign
  • Eureka
  • 面试
  • 工具
  • 项目
  • 关于
🌏本站
🧸GitHub (opens new window)
Home
算法
  • 手写Spring
  • SSM
  • SpringBoot
  • JavaWeb
  • JAVA基础
  • 容器
  • Netty

    • IO模型
    • Netty初级
    • Netty原理
  • JVM
  • JUC
  • Redis基础
  • 源码分析
  • 实战应用
  • 单机缓存
  • MySQL

    • 基础部分
    • 实战与处理方案
    • 面试
  • ORM框架

    • Mybatis
    • Mybatis_Plus
  • SpringCloudAlibaba
  • MQ消息队列
  • Nginx
  • Elasticsearch
  • Gateway
  • Xxl-job
  • Feign
  • Eureka
  • 面试
  • 工具
  • 项目
  • 关于
🌏本站
🧸GitHub (opens new window)
  • JAVA基础

  • 集合容器

  • Netty

  • JVM

  • JUC

    • 并发机制初识
      • 1.volatile
        • volatile修饰的变量如何保证可见性?
        • volatile使用优化
      • 2.synchronized
        • 2.1偏向锁
        • 2.2轻量级锁
        • 2.3对比
      • 3.CAS原子操作
    • JMM语义与重排
    • 多线程通信与编程应用
    • Lock并发锁原理
    • 并发容器与框架
    • 原子操作类
    • 并发工具类
    • 线程池
    • Executor框架
    • 并发编程实践
    • JUC面试
  • Java
  • JUC
phan
2023-09-29
目录

并发机制初识

# 并发机制初识

java代码编译成java字节码后,会被加载到JVM中执行,并转换成汇编指令在CPU上执行。因此java所有并发机制依赖于JVM的实现和CPU指令。

本章节主要介绍:

  • volatile底层处理器实现原理
  • synchronized的几种锁

# 1.volatile

可见性

共享变量的可见性指的是,当一个线程修改一个共享变量后,其它的线程能够读取到这个修改的值。

# volatile修饰的变量如何保证可见性?

为了提高处理效率,CPU不会直接和内存进行通信,而是通过CPU—缓存—内存的方式进行。对于多核处理器而言,每个处理器都有自己专门的缓存区,所有缓存区共享一个系统内存。

在对volatile修饰的变量执行写操作时,生成的汇编代码会多一个Lock指令,这个Lock指令只要做了两件事:

  1. 当前处理器缓存行的数据会写回到系统内存中
  2. 其它CPU缓存的该地址的数据失效

其它CPU会使用嗅探技术,保证CPU内部缓存、其它CPU内部缓存、系统内存的数据在总线上保持一致。

# volatile使用优化

缓存行填充

多个变量占据的字节小于缓冲行的大小,那么CPU会将多个变量读到同一个高速缓存行。

而如果个别处理器支持“缓存行填充”,那么不满足缓存行大小的变量会自动扩充,单独填满占据一个缓存行。

不支持“缓存行填充”的CPU,在读写某个变量时,可能会导致同一个缓存行内的其它变量同时被锁住,如上图CPU1在对head节点操作时,会导致其它多处理器都不能访问缓存行1其它变量。

字节追加:通过扩充volatile变量的字节大小,保证每个节点变量只占据一个缓存行存储。解决了多个共享变量在频繁写的过程中产生的锁定问题。

# 2.synchronized

synchronized用于实现代码同步:每个线程在访问同步代码块时,首先需要获取锁,执行完退出或者抛出异常时需要释放锁。

synchronized用的锁保存在java对象头中,java对象头包括以下几个部分:

  • Mark Word:存储对象的锁信息、hashcode。具体会根据锁标志位进行变化,不同的锁对应不同的Mark Word信息。
  • Class Metadata:存储对象元信息、数据、地址
  • (数组对象)

Java SE 1.6中,锁等级从低到高一共可以分为4种状态:无锁状态、偏向锁状态、轻量级锁、重量级锁。锁可以升级,但不能降级。

# 2.1偏向锁

为了使同一个线程多次访问同步块获取锁时,获取锁的代价更小,引入了偏向锁。

线程第一次获取锁时,在对象头中存储偏向锁的线程ID,以后该线程再次访问时,无需进行CAS操作加锁解锁(修改Mark Word)。

偏向锁撤销:当其它线程想要访问同步块,尝试通过CAS竞争获取偏向锁时,当前持有偏向锁的线程才会释放锁,将偏向锁偏向于其它线程,或是变为无锁状态。

# 2.2轻量级锁

轻量级加锁:线程首先将对象头的Mark Word复制到自己的栈帧中,然后通过CAS操作尝试将对象头的Mark Word修改为指向该锁记录栈帧的指针。如果成功,则当前线程获得轻量级锁,否则会不断自旋尝试竞争获取锁。

轻量级解锁:通过CAS操作将栈帧的锁记录 Displaced Mark Word替换为对象头。如果失败则表示当前存在多个线程同时竞争获取锁和释放锁,膨胀为重量级锁。

自旋操作会消耗CPU,如果当前锁处于重量级锁,那么试图获取锁的所有线程都会被阻塞住。直到获取锁的线程释放锁后,才会唤醒所有被阻塞的线程,进行新一轮的竞争。

# 2.3对比

  • 偏向锁:适用于只有单个线程访问同步块的情况。

  • 轻量级锁:用于不同线程访问同步块的情况。每个线程都会自旋获取锁,性能高,响应时间短。

  • 重量级锁:用于解决锁竞争消耗CPU的情况。锁竞争不消耗CPU,吞吐量高。但涉及线程阻塞和唤醒,进行上下文切换,响应时间长。

# 3.CAS原子操作

CAS无锁算法

如果“当前地址的值”与“先前读到的该地址的值”是一致,那么可以判定人为该变量没有被修改过,可以赋予新的值。否则不做任何修改。

循环CAS用于保证对共享变量操作时,是原子操作。

ABA问题:解决方式通过加版本号,判断当前第二个变量A是否已经被修改过。

编辑 (opens new window)
#JUC
上次更新: 2023/12/15, 15:49:57
JVM面试
JMM语义与重排

← JVM面试 JMM语义与重排→

Theme by Vdoing | Copyright © 2023-2024 blageCoder
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式