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.classLast modified 2024-1-11; size 309 bytesMD5 checksum 7baf13265d8f13f9acf4aacb6a6ef3b4Compiled from "CasExampleTest1.java"
public class ch12_thread.class2.CasExampleTest1minor version: 0major version: 52flags: 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: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 6: 0public void incr();descriptor: ()Vflags: ACC_PUBLICCode:stack=3, locals=1, args_size=10: aload_01: dup2: getfield      #2                  // Field i:I5: iconst_16: iadd7: putfield      #2                  // Field i:I10: returnLineNumberTable:line 9: 0line 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();// 预期结果 20000System.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=1t1.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.mzph.cn/news/616118.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一套成熟的Spring Cloud智慧工地平台源码,自主版权,支持二次开发!

智慧工地源码&#xff0c;java语言开发的智慧工地源码 智慧工地利用移动互联、物联网、云计算、大数据等新一代信息技术&#xff0c;彻底改变传统施工现场各参建方的交互方式、工作方式和管理模式&#xff0c;为建设集团、施工企业、监理单位、设计单位、政府监管部门等提供一揽…

RabbitMQ(十)队列的声明方式

目录 1.编程式声明补充&#xff1a;RabbitTemplate 和 AmqpAdmin 的区别 2.声明式声明补充&#xff1a;new Queue() 和 QueueBuilder.durable(queueName).build() 的区别 背景&#xff1a; 在学习 RabbitMQ 的使用时&#xff0c; 经常会遇到不同的队列声明方式&#xff0c;有的…

酚醛胶面建筑模板 — 广西厂家直销,质保可靠

在现代建筑行业中&#xff0c;选择高质量的建筑板材对于确保施工质量和工程安全至关重要。广西厂家直销的酚醛胶面建筑板&#xff0c;以其卓越的质量和可靠的质保&#xff0c;成为了建筑行业的优选材料。 产品特性 卓越的耐候性&#xff1a;我们的酚醛胶面建筑板采用高品质酚醛…

图文看懂Android的Matrix原理

Matrix结构 在Android开发中&#xff0c;矩阵是一个非常强大且有趣的工具。位于图形库中&#xff0c;android.graphics.Matrix 是一个 33 的 float 矩阵&#xff0c;其主要作用是坐标变换。 它的结构大概是这样的&#xff1a; 其中每个位置的数值作用和其名称所代表的的含义是…

Vue-18、Vue人员列表排序

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>列表排序</title><script type"text/javascript" src"https://cdn.jsdelivr.net/npm/vue2/dist/vue.js"></script…

Linux中DCHP与时间同步

目录 一、DHCP &#xff08;一&#xff09;工作原理 1.获取 2.续约 &#xff08;二&#xff09;分配方式 &#xff08;三&#xff09;服务器配置 1.随机地址分配 2.固定地址分配 二、时间同步 &#xff08;一&#xff09;ntpdate &#xff08;二&#xff09;chrony …

window-nginx注册服务(nginx-1.24.0.zip)

window-nginx注册服务(nginx-1.24.0.zip) 1、下载当前windows版nginx的稳定版本。 https://nginx.org/en/download.html 2、解压到指定目录中&#xff0c;这里解压到D盘根目录&#xff0c;D:\nginx-1.24.0 3、管理员打开命令行&#xff0c;可先进行相关操作&#xff0c;看一下n…

uni-app修改头像和个人信息

效果图 代码&#xff08;总&#xff09; <script setup lang"ts"> import { reqMember, reqMemberProfile } from /services/member/member import type { MemberResult, Gender } from /services/member/type import { onLoad } from dcloudio/uni-app impor…

Google的Ndk-Sample学习笔记之一(hello-jniCallback)

前言: 近段时间因为项目的需求,需要使用JNI,所以下载了Google的Ndk-Sample学习下,准备记录 下来,留给后期自己查看 问题点一:JNI_OnLoad方法必须返回JNI的版本 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {JNIEnv *env;memset(&g_ctx, 0, sizeof(g_…

亚马逊API:快速查询全球商品数据的技巧!

了解亚马逊API的限制和要求&#xff1a;在使用亚马逊API之前&#xff0c;您需要了解其限制和要求&#xff0c;例如请求频率限制、认证要求等。确保您遵循了API的使用条款&#xff0c;以避免不必要的麻烦。使用合适的亚马逊API服务&#xff1a;亚马逊提供了多个API服务&#xff…

Atlassian版本选择趋势是上云还是本地部署?全面分析两个版本的特性

近日&#xff0c;龙智联合Atlassian举办的DevSecOps研讨会年终专场”趋势展望与实战探讨&#xff1a;如何打好DevOps基础、赋能创新”在上海圆满落幕。龙智Atlassian技术与顾问咨询团队&#xff0c;以及清晖、JamaSoftware、CloudBees等生态伙伴的嘉宾发表了主题演讲&#xff0…

flutter封装dio请求库,让我们做前端的同学可以轻松上手使用,仿照axios的使用封装

dio是一个非常强大的网络请求库&#xff0c;可以支持发送各种网络请求&#xff0c;就像axios一样灵活强大&#xff0c;但是官网没有做一个demo示例&#xff0c;所以前端同学使用起来还是有点费劲&#xff0c;所以就想在这里封装一下&#xff0c;方便前端同学使用。 官网地址&a…

uniapp开发安卓应用微信开放平台创建应用如何获取签名

微信开放平台创建应用时需要应用的签名 比如我们开发了一个应用叫 “滴滴拉屎” 包名&#xff1a;uni.DIDILASHI #mermaid-svg-BUKbltDr30J93dUs {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-BUKbltDr30J93dUs .…

直播带货2024:洗牌、阵痛和暗流涌动

文 | 螳螂观察 作者 | 青月 一天前&#xff0c;大学生齐夏根本不会在直播间购买《额尔古纳河右岸》这种书籍。 她是喜欢看小说&#xff0c;但只钟爱悬疑无限流题材&#xff0c;至于《额尔古纳河右岸》这种讲述一个弱小民族顽强的抗争和优美的爱情的长篇小说&#xff0c;用齐…

vue上传文件加进度条,fake-progress一起使用

el-upload上传过程中加进度条&#xff0c;进度条el-progress配合fake-progress一起使用&#xff0c;效果如下&#xff1a; 安装 npm install fake-progress 在用到的文件里面引用 import Fakeprogress from "fake-progress"; 这个进度条主要是假的进度条&#xff…

轻量级图床Imagewheel本地部署并结合内网穿透实现远程访问

文章目录 1.前言2. Imagewheel网站搭建2.1. Imagewheel下载和安装2.2. Imagewheel网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar临时数据隧道3.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 4.公网访问测…

springIoc依赖注入循环依赖三级缓存

springIoc的理解&#xff0c;原理和实现 控制反转&#xff1a; 理论思想&#xff0c;原来的对象是由使用者来进行控制&#xff0c;有了spring之后&#xff0c;可以把整个对象交给spring来帮我们进行管理 依赖注入DI&#xff1a; 依赖注入&#xff0c;把对应的属性的值注入到…

全面解读数据安全法规

数据安全&#xff0c;可以说是近些年的热点&#xff0c;特别是随着大数据、人工智能等信息安全技术的快速发展&#xff0c;数据安全和隐私保护形势日益严峻&#xff0c;网络边界被打破&#xff0c;数据安全问题与日俱增。各国也非常重视数据安全建设&#xff0c;如下图展示的全…

Android Matrix (三)矩阵组合和应用变换

在 Android 开发中&#xff0c;Matrix 类不仅提供了 mapPoints 方法来变换点坐标&#xff0c;还提供了多种其他用法&#xff0c;使其成为处理图像和视图变换的强大工具。以下是 Matrix 类的一些关键用法&#xff1a; 1. 变换方法 setTranslate(float dx, float dy): 设置矩阵…

Android开发基础(二)

Android开发基础&#xff08;二&#xff09; 上篇主要描述了Android系统架构&#xff0c;代码是通过Java表示的&#xff1b; 本篇将从介绍Android组件去理解Android开发&#xff0c;代码将对Java和Kotlin进行对比。 Android组件 Android应用程序由一些零散的有联系的组件组成…