Java 多线程学习(三)

目录

一、多线程

1、深入synchronized

1.1、synchronized的优化

1.2、synchronized实现原理

1.3、synchronized的锁升级

1.4、重量锁底层ObjectMonitor


一、多线程

1、深入synchronized

1.1、synchronized的优化

    在JDK1.5的时候,Doug Lea推出了ReentrantLock,lock的性能远高于synchronized,所以JDK团队就在JDK1.6中,对synchronized做了大量的优化。

    锁消除:在synchronized修饰的代码中,如果不存在操作临界资源的情况,会触发锁消除,你即便写了synchronized,它也不会触发。

public synchronized void method(){
    // 没有操作临界资源
    // 此时这个方法的synchronized你可以认为没有
}

    锁膨胀:如果在一个循环中,频繁的获取和释放做资源,这样带来的消耗很大,锁膨胀就是将锁的范围扩大,避免频繁的竞争和获取锁资源带来不必要的消耗。

public void method() {
    Object o = new Object();
    for (int i = 0; i < 999999; i++) {
        synchronized (o) {

        }
    }
    // 这时上面的代码会触发锁膨胀
    synchronized (o) {
        for (int i = 0; i < 999999; i++) {

        }
    }
}

    锁升级:ReentrantLock的实现,是先基于乐观锁的CAS尝试获取锁资源,如果拿不到锁资源,才会挂起线程。synchronized在JDK1.6之前,完全就是获取不到锁,立即挂起当前线程,所以synchronized性能比较差。

    synchronized就在JDK1.6做了锁升级的优化

    无锁、匿名偏向:当前对象没有作为锁存在。

    偏向锁:如果当前锁资源,只有一个线程在频繁的获取和释放,那么这个线程过来,只需要判断,当前指向的线程是否是当前线程 。

        如果是,直接拿着锁资源走。

        如果不是,基于CAS的方式,尝试将偏向锁指向当前线程。如果获取不到,触发锁升级,升级为轻量级锁。(偏向锁状态出现了锁竞争的情况)

    轻量级锁:会采用自旋锁的方式去频繁的以CAS的形式获取锁资源(采用的是自适应自旋锁)

        如果成功获取到,拿着锁资源走。

        如果自旋了一定次数,没拿到锁资源,锁升级。

    重量级锁:就是最传统的synchronized方式,拿不到锁资源,就挂起当前线程。(用户态&内核态)

1.2、synchronized实现原理

    synchronized是基于对象实现的。

    先要对Java中对象在堆内存的存储有一个了解。

    对象存储信息包括:对象头,实例数据,对象填充等。其中对象头包括MarkWord,ClassPoint。

    对象的锁信息都是存储在MarkWord中的。

    展开MarkWord如下图

MarkWord中标记着四种锁的信息:无锁、偏向锁、轻量级锁、重量级锁等

1.3、synchronized的锁升级

为了可以在Java中看到对象头的MarkWord信息,需要导入依赖

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.17</version>
</dependency>

    锁默认情况下,开启了偏向锁延迟。

    偏向锁在升级为轻量级锁时,会涉及到偏向锁撤销,需要等到一个安全点(STW),才可以做偏向锁撤销,在明知道有并发情况,就可以选择不开启偏向锁,或者是设置偏向锁延迟开启。

    因为JVM在启动时,需要加载大量的.class文件到内存中,这个操作会涉及到synchronized的使用,为了避免出现偏向锁撤销操作,JVM启动初期,有一个延迟4s开启偏向锁的操作

     如果正常开启偏向锁了,那么不会出现无锁状态,对象会直接变为匿名偏向。

import org.openjdk.jol.info.ClassLayout;

public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(5000);
        Object o = new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());

        new Thread(() -> {
            synchronized (o) {
                //t1  - 偏向锁
                System.out.println("t1:" + ClassLayout.parseInstance(o).toPrintable());
            }
        }).start();
        //main - 偏向锁 - 轻量级锁CAS - 重量级锁
        synchronized (o) {
            System.out.println("main:" + ClassLayout.parseInstance(o).toPrintable());
        }
    }
}

运行结果:可以注释运行t1或main运行

java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0x00000e80
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

main:java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000e4517ff700 (thin lock: 0x000000e4517ff700)
  8   4        (object header: class)    0x00000e80
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t1:java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000002210065f872 (fat lock: 0x000002210065f872)
  8   4        (object header: class)    0x00000e80
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

整个锁升级状态的转变:

1.4、重量锁底层ObjectMonitor

需要找到openjdk,找到ObjectMonitor的两个文件,hpp,cpp

objectMonitor.hpp

ObjectMonitor() {
    _header       = NULL;   // header存储着MarkWord
    _count        = 0;      // 竞争锁的线程个数
    _waiters      = 0,      // wait的线程个数
    _recursions   = 0;      // 标识当前synchronized锁重入的次数
    _object       = NULL;
    _owner        = NULL;   // 持有锁的线程
    _WaitSet      = NULL;   // 保存wait的线程信息,双向链表
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;  // 获取锁资源失败后,线程要放到当前的单向链表中
    FreeNext      = NULL ;
    _EntryList    = NULL ;  // _cxq以及被唤醒的WaitSet中的线程,在一定机制下,会放到EntryList中
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

objectMonitor.cpp

TryLock

int ObjectMonitor::TryLock (Thread * Self) {
   for (;;) {
	  // 拿到持有锁的线程
      void * own = _owner ;
      // 如果有线程持有锁,告辞
      if (own != NULL) return 0 ;
      // 说明没有线程持有锁,own是null,cmpxchg指令就是底层的CAS实现。
      if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {
		 // 成功获取锁资源
         return 1 ;
      }
      // 这里其实重试操作没什么意义,直接返回-1
      if (true) return -1 ;
   }
}

try_entry

bool ObjectMonitor::try_enter(Thread* THREAD) {
  // 在判断_owner是不是当前线程
  if (THREAD != _owner) {
    // 判断当前持有锁的线程是否是当前线程,说明轻量级锁刚刚升级过来的情况
    if (THREAD->is_lock_owned ((address)_owner)) {
       _owner = THREAD ;
       _recursions = 1 ;
       OwnerIsThread = 1 ;
       return true;
    }
    // CAS操作,尝试获取锁资源
    if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
      // 没拿到锁资源,告辞
      return false;
    }
    // 拿到锁资源
    return true;
  } else {
    // 将_recursions + 1,代表锁重入操作。
    _recursions++;
    return true;
  }
}

enter(想方设法拿到锁资源,如果没拿到,挂起扔到_cxq单向链表中)

void ATTR ObjectMonitor::enter(TRAPS) {
  // 拿到当前线程
  Thread * const Self = THREAD ;
  void * cur ;
  // CAS
  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
  if (cur == NULL) {
     // 拿锁成功
     return ;
  }
  // 锁重入操作
  if (cur == Self) {
     _recursions ++ ;
     return ;
  }
  //轻量级锁过来的。
  if (Self->is_lock_owned ((address)cur)) {
    _recursions = 1 ;
    _owner = Self ;
    OwnerIsThread = 1 ;
    return ;
  }
  // 走到这了,没拿到锁资源,count++
  Atomic::inc_ptr(&_count);
    for (;;) {
      jt->set_suspend_equivalent();
      // 入队操作,进到cxq中
      EnterI (THREAD) ;
      if (!ExitSuspendEquivalent(jt)) break ;
      _recursions = 0 ;
      _succ = NULL ;
      exit (false, Self) ;
      jt->java_suspend_self();
    }
  }
  // count--
  Atomic::dec_ptr(&_count);
}

EnterI

for (;;) {
    // 入队
    node._next = nxt = _cxq ;
    // CAS的方式入队。
    if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
    // 重新尝试获取锁资源
    if (TryLock (Self) > 0) {
        assert (_succ != Self         , "invariant") ;
        assert (_owner == Self        , "invariant") ;
        assert (_Responsible != Self  , "invariant") ;
        return ;
    }
}

Java 多线程学习(二)

一个程序员最重要的能力是:写出高质量的代码!!
有道无术,术尚可求也,有术无道,止于术。
无论你是年轻还是年长,所有程序员都需要记住:时刻努力学习新技术,否则就会被时代抛弃!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/251617.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis核心知识小结

基础 redis为什么快呢&#xff1f; 单线程基于io多路复用底层C语言对数据结构做了优化完全内存的操作 Redis6.0使用多线程是怎么回事? Redis不是说用单线程的吗&#xff1f;怎么6.0成了多线程的&#xff1f; Redis6.0的多线程是用多线程来处理数据的读写和协议解析&#x…

【算法笔记】动态规划,使用最小花费爬楼梯,详细刨析。

1.题目描述 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 请你计算并返回达到楼梯顶部的最低花费。 示…

linux 开机启动流程

1.打开电源 2.BIOS 有时间和启动方式 3.启动Systemd 其pid为1 4.挂载引导分区 /boot 5.启动各种服务 如rc.local

STM32 EC200 物联网项目实操 第2篇 FTP OTA升级

背景&#xff1a; 做了个物联网项目&#xff0c;需要做个OTA升级&#xff0c;程序分为两部分&#xff0c;一部分是BOOT引导程序&#xff0c;一部是主程序&#xff0c;在BOOT引导程序里面实现了和EC200 4G模块通讯&#xff0c;和FTP服务器通讯&#xff0c;获取OTA升级BIN文件。主…

【前端八股】系列之性能指标与评估工具

【前端八股】系列之性能指标与评估工具 前言性能指标的定义和特性性能指标的分类实验室指标真实指标用户感知指标评估工具 前言 这里是以前自己关于性能指标与评估工具的相关笔记&#xff0c;下面主要是关于性能指标的定义与特性以及相关的评估工具&#xff0c;并没有记录深入…

人工智能_机器学习065_SVM支持向量机KKT条件_深度理解KKT条件下的损失函数求解过程_公式详细推导---人工智能工作笔记0105

之前我们已经说了KKT条件,其实就是用来解决 如何实现对,不等式条件下的,目标函数的求解问题,之前我们说的拉格朗日乘数法,是用来对 等式条件下的目标函数进行求解. KKT条件是这样做的,添加了一个阿尔法平方对吧,这个阿尔法平方肯定是大于0的,那么 可以结合下面的文章去看,也…

mysql8支持远程访问

上面的localhost要改为%号就打开了远程访问 ALTER USER root% IDENTIFIED WITH mysql_native_password BY fengzi2141;

vite原理

一、依赖预构建 1、为什么需要依赖预构建 CommonJS和UMD兼容性 在开发阶段中&#xff0c;vite的开发服务器将所有的代码视为原生ES模块。因此&#xff0c;vite必须先将作为CommonJS或者UMD发布的依赖项转换为ESM。 这是vite的一个特色&#xff0c;也是为什么会相对于webpack比…

Android动画(一)——逐帧动画

目录 介绍 Android动画类型分类 逐帧动画 逐帧动画的使用 效果图 介绍 Android动画是一种用于创建视觉效果和交互体验的技术&#xff0c;可以增强用户界面的吸引力和响应性。Android提供了丰富的动画框架和API&#xff0c;使开发者可以轻松地添加动画效果到他们的应用程序中…

n维随机变量、n维随机变量的分布函数

设随机试验E的样本空间是&#xff0c;其中表示样本点。 设是定义在上的随机变量&#xff0c;由它们构成一个n维向量&#xff0c;叫做n维随机向量&#xff0c;也叫n维随机变量。 对于任意n个实数&#xff0c;n元函数 称为n维随机变量的分布函数&#xff0c;也叫联合分布函数。

springCloud项目打包如何把jar放到指定目录下

springCloud项目打包如何把jar发放到指定目录下 maven-antrun-plugin springCloud微服务打包jar&#xff0c;模块过多&#xff1b;我的项目模块结构如下&#xff1a; 我把实体类相关的单独抽离一个模块在service-api下服务单独写在service某块下&#xff0c; 每个模块的jar都…

【C++】 C++11 新特性探索:decltype 和 auto

▒ 目录 ▒ &#x1f6eb; 问题描述环境 1️⃣ decltype推导变量类型推导函数返回类型 2️⃣ auto自动推导变量类型迭代器和范围循环 3️⃣ decltype 和 auto 同时使用&#x1f6ec; 结论&#x1f4d6; 参考资料 &#x1f6eb; 问题 描述 C11 引入了一些强大的新特性&#xff…

【python】Debian安装miniconda、spyder、tushare

1. miniconda 安装 — 动手学深度学习 2.0.0 documentation中有安装Miniconda的一些说明。 Miniconda — miniconda documentation是Miniconda网站&#xff0c;里面也有安装说明。 Debian安装按照linux安装即可&#xff1a; mkdir -p ~/miniconda3 wget https://repo.anaco…

软实力篇---第五篇

系列文章目录 文章目录 系列文章目录前言一、HR 最喜欢问程序员的 20 个问题二、面试中的礼仪与举止前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 一、HR 最喜欢…

机器学习---协同过滤

协同过滤&#xff08;Collaborative Filtering&#xff09;技术&#xff0c;是推荐系统中应用最为广泛的技术之一&#xff0c;协同过滤算法主要有两种&#xff0c;一种是基于用户的协同过滤算法(UserBaseCF)&#xff0c;另一种是基于物品的协同过滤算法(ItemBaseCF&#xff09;…

直接插入排序_希尔排序

文章目录 直接插入排序原理步骤视频演示代码实现特性 希尔排序原理步骤图像演示代码实现希尔排序的分析特性 直接插入排序和希尔排序的比较 直接插入排序 直接插入排序&#xff08;Straight InsertionSort&#xff09;是一种最简单的排序方法&#xff0c;其基本操作是将一条记录…

腾讯云:AI云探索之路

随着科技的飞速发展&#xff0c;人工智能(AI)云计算领域日益显现出其巨大的潜力和价值。在这个充满挑战和机遇的领域&#xff0c;腾讯云凭借其卓越的技术和创新能力&#xff0c;取得了令人瞩目的成果。本文将深入探讨腾讯云在AI云计算领域的优势&#xff0c;以及其为人工智能发…

Multi-sensor KIT-V3.0 多传感器开发板

Multi-sensor KIT-V3.0 多传感器开发板 1.前言1.1 特点 2. Multi-sensor KIT QMA8658A-EVB QMC5883评估板的扩展2.1 特点 3. Multi-sensor KIT &#xff08;QMA8658A-EVB QMC5883&#xff09; Ultrasonic-CH-X01-EVB评估板的扩展3.1 特点 4.资料资源Multi-sensor KIT-V3.0 …

基于Hadoop的农产品价格信息检测分析系统

基于Hadoop的农产品价格信息检测分析系统 前言数据处理模块1. 数据爬取2. 数据清洗与处理3. 数据存储 数据分析与检测模块1. 农产品价格趋势分析2. 农产品价格检索3. 不同市场价格对比 创新点 前言 为了更好地了解农产品市场价格趋势和不同市场之间的价格差异&#xff0c;我设…

swing快速入门(十五)

注释很详细&#xff0c;直接上代码 上一篇 新增内容 1.文件对话框&#xff08;保存文件&#xff09; 2.文件对话框&#xff08;打开文件&#xff09; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener;public class swing_tes…