JVM-JVM支持高并发底层原理精讲

一、透彻掌握高并发-从理解JVM开始

二、从线程的开闭看JVM的作用

1.run方法

启动start方法,会调用底层C++方法,告诉操作系统当前线程处于可运行状态,而如果直接调用run方法,则就不是以线程的方式来运行了,只是当做一个普通的方法来执行。

2.stop方法 -不推荐使用,stop是强制执行,不管是否正在运行,在做什么,直接停止。

3.如何中断一个阻塞线程

关闭处于阻塞中的线程:

按照上面关闭普通线程的方式,来关闭阻塞中的线程,会发现报了一个异常,首先需要明确的是,

这个异常不是错误。

继续修改下代码

当线程即使处于阻塞的时候,线程不再收到信号,线程也是可以收到一个异常,可以这个异常理解为一个信号,就像闹钟一样就会响,强制这个线程做出一定的响应,而这个异常就是这个子线程的那种,当调用线程终止方法,就会触发这个异常。

这里为什么没有停止呢,这是因为当阻塞的时候,父线程只能给子线程发停止的信号,要不要停止子线程说了算。

再修改下代码

重新执行,子线程自己停止了线程(即Main方法的thread.interrupt()只是发了一个停止的信号,实际子线程停止是子线程自己负责执行)。

这也就是为什么一般写代码遇见wait、sleep要加这个异常。

大白话:

        Java线程调用start(),JVM通过C++调用操作系统的线程接口,由操作系统创建一个线程,再由CPUrun(执行)这个线程;

        同理Java调通stop或interrupt(),JVM通过C++调用操作系统的线程停止接口,再由CPU收到stop命令停止线程。

三、原子性问题的产生原因与解决方案

package ch12_thread.class2;

/**
 * 测试线程的原子性
 */
public class CasExampleTest1 {
    private int i;
    public void incr(){
        i++;
    }
}
mac@MacdeMBP class2 % javap -v CasExampleTest1.class 
Classfile /Users/mac/IdeaProjects/OOM/JVMSample/src/main/java/ch12_thread/class2/CasExampleTest1.class
  Last modified 2024-1-11; size 309 bytes
  MD5 checksum 7baf13265d8f13f9acf4aacb6a6ef3b4
  Compiled from "CasExampleTest1.java"
public class ch12_thread.class2.CasExampleTest1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#15         // ch12_thread/class2/CasExampleTest1.i:I
   #3 = Class              #16            // ch12_thread/class2/CasExampleTest1
   #4 = Class              #17            // java/lang/Object
   #5 = Utf8               i
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               incr
  #12 = Utf8               SourceFile
  #13 = Utf8               CasExampleTest1.java
  #14 = NameAndType        #7:#8          // "<init>":()V
  #15 = NameAndType        #5:#6          // i:I
  #16 = Utf8               ch12_thread/class2/CasExampleTest1
  #17 = Utf8               java/lang/Object
{
  public ch12_thread.class2.CasExampleTest1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 6: 0

  public void incr();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field i:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field i:I
        10: return
      LineNumberTable:
        line 9: 0
        line 10: 10
}
SourceFile: "CasExampleTest1.java"

改造成多线程代码

package ch12_thread.class2;

public class AtomicExample {
    private int i = 0;

    public void incr() {
        i++;
    }

    public static void main(String[] args) throws InterruptedException {
        final AtomicExample atomicExample = new AtomicExample();
        Thread[] threads = new Thread[2];

        for(int j = 0; j < 2; j++){
            threads[j] = new Thread(() -> {
                for (int k = 0; k < 10000; k++){
                    atomicExample.incr();
                }
            });
            threads[j].start();
        }

        threads[0].join();
        threads[1].join();
        // 预期结果 20000
        System.out.println(atomicExample.i);
    }
}

运行结果:

结果13644与预期20000不符,CPU切换导致原子性问题。

修改代码, incr方法加同步锁

再次执行

执行结果与预期结果一致。

大白话:

        (1)在早期32位,这时一个Long型数据非常大62位,会将数据分为低32位、高32位,最后合在一起,这时中间被打断,这个数据可能就不准确了,而CPU层保证内存操作原子性就是说CPU去读取数据保证内存操作的原子性,中间不会发生中断,保证数据读取准确。

        (2)CPU和数据之间通过公共总线通信,当CPU-1读取变量时,给总线加锁,保证这个变量不能被其他CPU读取,当CPU-1操作完,将数据存回内存后,在放开总线锁,其他CPU继续访问操作。

        (3)对Cache加锁,后续课程解释。

大白话:

        临界区加锁,就是对临界区资源(数据)加锁,保证操作安全,操作完后解锁。缺点,耗时,不需要加锁的也加锁了,影响性能。

四、CAS与乐观锁原理

乐观锁 - 将数据比较判断提交到汇编层面执行,保证数据一致性,没有发生冲突继续执行,如果发生冲突再想办法解决。

前面的代码是通过加synchronized同步锁完成的,现在通过在不加锁使用原子类完成

五、可见性问题的本质

package ch12_thread.class5;

public class VolatileExample {

    public static boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
           int i = 0;
           while (!stop) {
               i++;
           }
            System.out.println("finish while ...");
        });

        t1.start();
        System.out.println("t1 start ...");
        Thread.sleep(1000);
        stop = true;
    }
}

上面的子线程是否收到stop变量的变化,并最终终止循环输入"finish while ..."

执行结果:

发现在主线程改变静态变量的值,子线程是看不到的变化的。

继续修改代码,给stop变量加上voliatile参数

运行结果:

造成这种情况的原因:

        线程内部、CPU有缓存,当变量改变时,线程之间、CPU之间感知不到。

当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值

这里涉及到MSEI缓存一致性协议,具体讲解见

白话MESI缓存一致性协议_msei-CSDN博客

六、顺序性问题的本质和valatile的源码实现原理screenflow

package class6;

/**
 * 顺序性问题演示
 */
public class MemoryReorderingExample {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {
        int count = 0;
        while (true) {
            x = 0;
            y = 0;
            a = 0;
            b = 0;

            count++;

            Thread t1 = new Thread(() -> {
                a = 1;
                x = b;
            });

            Thread t2 = new Thread(() -> {
                b = 1;
                y = b;
            });

            // 情况1.t1先执行,y=1, x=0
            // 情况2.t2先执行,x=1, y=0
            // 情况3.t1和t2同时执行,x=1, y=1

            t1.start();
            t2.start();
            t1.join();
            t2.join();

            if(x == 0- && y == 0){
                System.out.println("第" + count + "次 x=" + x + "y=" + y);
                break;
            }
        }
    }
}

运行结果:

正常情况下,不应该出现x==0&&y==0,但是实际测试结果,出现了x==0&&y==0。

为什么会出现这种情况呢,其实是因为出现了指令重排序问题。

当t1线程的x=b,从a=1代码的下面,移动到a=1代码的上面,且t2线程的y=a,从b=1代码的下面移动到b=1代码的上面,就会计算出x==0&&y==0,这种问题就是顺序性问题

编译器优化场景举例:

        上图这种情况,编译器认为左边代码太消耗资源,会自动优化成右边代码。

那么针对前面的这种重排序问题,怎么解决呢?

最简单的解决方法,是加锁!

多运行一些时间,没有发现问题

刚才的案例里,使用synchronized关键字,主要作用是加锁,最大缺点是性能低。

大白话:

        java层面设置volatile,JVM转换成C++程序,C++通过操作系统操作一系列硬件指令,通过操作内存屏障保证顺序性问题,通过lock锁操作缓存行,保证不同的缓存之间是一致的。

七、Java里的对象到底是什么

大白话:

        这里的加锁即synchronized,加锁后具体是哪种锁(偏向锁、轻量级锁、重量级锁) ,都有可能。

        堆中对象能否使用,基于以下几种状态判断:

        1.无锁 - 堆中的对象无锁,可以直接使用;

        2.偏向锁 - 堆中对象被线程占用,对象的对象头会存在偏向锁,保存线程ID,别的线程发现这个对象中有线程信息了,被标记偏向锁,就不能使用了;

        3.轻量级锁 - 对象竞争比较弱,就会采用轻量级锁;

        4.重量级锁 - 好多线程都来访问同一个对象,对象竞争比较强,这个对象加重量级锁,第一个线程处理完了,第二个线程再使用,依次使用。

通过JOL查看Java对象信息

<dependencies>
    <dependency>
      <groupId>org.openjdk.jol</groupId>
      <artifactId>jol-core</artifactId>
      <version>0.9</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
package class6;

public class MyObject {
}
package class6;

import org.openjdk.jol.info.ClassLayout;

// -XX:+UseCompressedOops 默认开启的压缩所有指针
// -XX:+UseCompressedClassPointers 默认开启的压缩对象头的类型执行Klass Pointer
// Oops : Ordinary Object Pointers
public class JOLSample {
    public static void main(String[] args) {
        ClassLayout layout = ClassLayout.parseInstance(new MyObject());
        System.out.println(layout.toPrintable());
    }
}

开启压缩后

占了12个字节,不够8的整数倍,还差4个字节,这四个字节,也就是说还可以放其他信息。

八、synchronized锁的状态与实现原理

1.无锁

对应

说明是无锁状态。

2.偏向锁

在同一时刻,有且只有一个线程执行了这个同步锁方法,而且并没有发生竞争的情况,这个时候锁的状态就是偏向锁。

对应

3.轻量级锁

已经发生多线程冲突了,但是不太严重,具体实现CAS(乐观锁)。

当没有多线程访问的状态,就是轻量级锁。

对应锁标志位00

4.重量级锁

很多线程访问对象时,对象已经被锁住,这时,其他线程也来抢占对象,则升级为重量级锁。 

演示代码:

package class6;

import org.openjdk.jol.info.ClassLayout;

import java.util.concurrent.TimeUnit;

public class HeavyLockExample {
    public static void main(String[] args) throws InterruptedException {
        final HeavyLockExample heavy = new HeavyLockExample();
        System.out.println("加锁之前");
        System.out.println(ClassLayout.parseInstance(heavy).toPrintable());
        Thread t1 = new Thread(() -> {
           synchronized (heavy){
               try{
                   TimeUnit.SECONDS.sleep(2);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t1.start();
        //确保t1线程已经运行
        TimeUnit.MILLISECONDS.sleep(500);
        System.out.println("t1线程抢占了锁");
        System.out.println(ClassLayout.parseInstance(heavy).toPrintable());
        synchronized (heavy) {
            System.out.printf("main线程来抢占锁");
            System.out.printf(ClassLayout.parseInstance(heavy).toPrintable());
        }
//        System.gc();
//        System.out.printf(ClassLayout.parseInstance(heavy).toPrintable());
    }
}

一开始,heavy无锁

t1抢占后,heavy变为轻量级锁

main线程,再去抢占对象,变为重量级锁

注:偏向锁不太好演示出来。

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

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

相关文章

变电 | 主变压器异常处理案例两则

【案例一】 【案例二】 最近省企业联合会公布了 优秀企业管理论文结果 去年年末投的论文 获得了二等奖 巴适

先爬、再行、最后跑,“流程挖掘之父”Wil教授谈流程挖掘的突破之路

商界有句俗话&#xff1a;“先爬&#xff0c;再行&#xff0c;最后跑”。这正是实现有价值突破的过程。 作者 | Wil van der Aalst教授 海明威在他的某部作品中描绘了这样一幕&#xff1a;有人询问如何走向破产&#xff0c;得到的答案是“开始时循序渐进&#xff0c;之后突然…

评论转换输出 - 华为OD统一考试

OD统一考试 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 在一个博客网站上&#xff0c;每篇博客都有评论。每一条评论都是一个非空英文字母字符串。 评论具有树状结构&#xff0c;除了根评论外&#xff0c;每个评论都有一个父评论。当评论保存时&am…

重新分区扩展C盘

电脑 – 管理 使用第三方工具&#xff1a;DiskGenius数据恢复及分区管理软件 要选择完成后重启 &#xff0c;如果这里忘记勾选&#xff0c;后面也会再次提醒并默认勾选重启 "调整后容量"是指图片上显示的非C盘之外的盘符的容量&#xff0c;这里指E盘大小 上面已经利…

做一个个人博客第一步该怎么做?

做一个个人博客第一步该怎么做&#xff1f; 好多零基础的同学们不知道怎么迈出第一步。 那么&#xff0c;就找一个现成的模板学一学呗&#xff0c;毕竟我们是高贵的Ctrl c v 工程师。 但是这样也有个问题&#xff0c;那就是&#xff0c;那些模板都&#xff0c;太&#xff01;…

运动模型非线性扩展卡尔曼跟踪融合滤波算法(Matlab仿真)

卡尔曼滤波的原理和理论在CSDN已有很多文章&#xff0c;这里不再赘述&#xff0c;仅分享个人的理解和Matlab仿真代码。 1 单目标跟踪 匀速转弯&#xff08;CTRV&#xff09;运动模型下&#xff0c;摄像头输出目标状态camera_state [x, y, theta, v]&#xff0c;雷达输出目标状…

【浅尝C++】引用

&#x1f388;归属专栏&#xff1a;浅尝C &#x1f697;个人主页&#xff1a;Jammingpro &#x1f41f;记录一句&#xff1a;大半夜写博客的感觉就是不一样&#xff01;&#xff01; 文章前言&#xff1a;本篇文章简要介绍C中的引用&#xff0c;每个介绍的技术点&#xff0c;在…

井盖异动传感器,守护脚下安全

随着城市化进程的加速&#xff0c;城市基础设施的安全问题日益受到关注。其中&#xff0c;井盖作为城市地下管道的重要入口&#xff0c;其安全问题不容忽视。然而&#xff0c;传统的井盖监控方式往往存在盲区&#xff0c;无法及时发现井盖的异常移动。为此&#xff0c;我们推出…

数据库与低代码:加速开发,提升效率的完美结合

随着技术的不断进步&#xff0c;数据库和低代码开发成为了现代应用程序开发中的两大关键要素。本文将探讨如何通过结合数据库和低代码开发&#xff0c;加速应用程序的开发过程&#xff0c;并提高开发效率和质量。 在过去的几十年中&#xff0c;数据库一直被视为应用程序开发中不…

【Linux进程】查看进程fork创建进程

目录 前言 1. 查看进程 2. 通过系统调用创建进程-fork初识 总结 前言 你有没有想过在使用Linux操作系统时&#xff0c;后台运行的程序是如何管理的&#xff1f;在Linux中&#xff0c;进程是一个非常重要的概念。本文将介绍如何查看当前运行的进程&#xff0c;并且讨论如何使用…

Sip - Ubuntu 配置 miniSIPServer 服务器(测试用)

客户提供的账号过期了&#xff0c;简单搭建 SIP 服务器&#xff0c;以便测试使用。个人认为这个配置起来最为简单&#xff0c;且测试功能足够。 官网miniSIPServer - 基于 Windows 以及 Linux 平台的 VoIP (SIP) 服务器软件. miniSIPServer 可能是最容易使用的 VoIP(SIP) 服务器…

获取进行逗号分隔的id值 Split的使用

获取进行逗号分隔的id值,Split的使用 后台实现对有逗号进行分割的字符串 使用这行代码就不会有一个空数组值,直接过滤调数组中的空值 var ids = key.Split(,).Where(s => !string.IsNullOrEmpty(s

进行交流负载测试的步骤和规范

交流负载测试是一种评估系统在正常或峰值负载下的性能和稳定性的测试方法。以下是进行交流负载测试的步骤和规范&#xff1a; 1. 确定测试目标&#xff1a;首先&#xff0c;需要明确测试的目标&#xff0c;例如&#xff0c;测试系统的响应时间、吞吐量、错误率等。 2. 设计测试…

Linux系统操作命令

Linux管理 在线查询Linux命令&#xff1a; https://www.runoob.com/linux/linux-install.htmlhttps://www.linuxcool.com/https://man.linuxde.net/ 1.Linux系统目录结构 Linux系统的目录结构是一个树状结构&#xff0c;每一个文件或目录都从根目录开始&#xff0c;并且根目…

双亲委派机制[人话版]

本篇文章仅作为记录学习之用,不具有参考价值. 如果您想系统学习,请移步最下方参考资料. 介绍 今天逛了一下牛客网, 看到有面试问到了双亲委派机制是什么, tomcat有没有打破双亲委派 , 瞬间懵逼, 听都没听过的名字, 听着就稀奇古怪. 然后翻了一下网上的答案,大概了解怎么回事.…

Python自动化测试数据驱动解决数据错误

数据驱动将测试数据和测试行为完全分离&#xff0c;实施数据驱动测试步骤如下&#xff1a; A、编写测试脚本&#xff0c;脚本需要支持从程序对象、文件或者数据库读入测试数据&#xff1b; B、将测试脚本使用的测试数据存入程序对象、文件或者数据库等外部介质中&#xff1b;…

知识库软件有很多,这几个最好用

时代进步的同时&#xff0c;逐渐优化的企业知识库已经成为企业优化工作效率、提升企业竞争力的重要工具。随着云计算和大数据技术的快速发展&#xff0c;知识库软件如雨后春笋般出现在人们的视野中。下面&#xff0c;我从寻宝者的角度&#xff0c;向大家稳稳地推荐三款最优秀的…

mp-html 微信原生小程序渲染富文本

引入组件 "usingComponents": {"mp-html": "/components/mp-html/index"}使用 <mp-html content"{{info.course_info.info}}" />获取组件 介绍 mp-html&#xff0c;小程序富文本解析利器 全面支持html标签 小程序大多数都是…

C++重新认知:拷贝构造函数

一、什么是拷贝构造函数 对于简单变量来说&#xff0c;可以轻松完成拷贝。 int a 10; int b a;但是对于复杂的类对象来说&#xff0c;不仅存在变量成员&#xff0c;也存在各种函数等。因此相同类型的类对象是通过拷贝构造函数来完成复制过程的。 #include<iostream>…

使用Notepad++将多行数据合并成一行

步骤 1、按CtrlF&#xff0c;弹出“替换”的窗口&#xff1b; 2、选择“替换”菜单&#xff1b; 3、“查找目标”内容输入为&#xff1a;\r\n&#xff1b; 4、“替换为”内容为空&#xff1b; 5、“查找模式”选择为正则表达式&#xff1b; 6、设置好之后&#xff0c;点击“全…