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.hqwc.cn/news/342822.html

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

相关文章

【CSS】首个字符占用多行,并自定义样式

效果 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>首字母大写</title><style&…

奥伦德光电耦合器5G通信领域及其相关领域推荐

光电耦合器是以光为媒介传输电信号的一种电-光-电转换器件。由于该器件使用寿命长、工作温度范围宽&#xff0c;所以在过程控制、工业通信、家用电器、医疗设备、通信设备、计算机以及精密仪器等方面有着广泛应用在当前工艺技术持续发展与提升的过程中&#xff0c;其工作速度、…

mybatisPlus 将List<String>字段转成json字符串,使用JacksonTypeHandler以及自定义类型处理器实现

文章目录 场景使用JacksonTypeHandler实现类型转换自定义StringListTypeHandler处理器实现 场景 项目中经常需要将List转成json存储到数据库中, mybatisPlus默认实现了JacksonTypeHandler&#xff0c;GsonTypeHandler&#xff0c;FastjsonTypeHandler&#xff0c;也可以自定义类…

Dart 空感知操作符:??

示例 写了如下代码&#xff1a; var str1 "hello"; var str2 "world"; var result str1 ?? str2.toUpperCase(); //如果str1不为空&#xff0c;则执行后面的语句 print(result); 代码可以正常执行&#xff0c;但是报了如下错误&#xff1a; Warnin…

【操作系统】重点概念汇总(手写版本)

Chapter1&#xff1a;操作系统引论 Chapter2&#xff1a;进程管理 Chapter3&#xff1a;处理机调度与死锁 Chapter4&#xff1a;存储器管理 Chapter5&#xff1a;虚拟存储器 Chapter6&#xff1a;输入输出系统 Chapter7&#xff1a;文件管理 Chapter8&#xff1a;磁盘存…

开放式蓝牙耳机学生党买哪款好?2024适合学生党入手的几款耳机分享

开放式耳机&#xff0c;就是那种不会把耳朵完全封闭起来的耳机&#xff0c;相比于封闭式耳机&#xff0c;开放式耳机有着更多的通风设计&#xff0c;长时间佩戴耳朵都不会疼痛&#xff0c;市面上涌现出许多适合学生党购买的开放式蓝牙耳机&#xff0c;那么如何选择一款性价比高…

如何进行有竞争力的SEO审计以超越行业竞争对手

许多营销人员都有兴趣密切关注竞争对手的搜索引擎优化 &#xff08;SEO&#xff09;。这是有道理的——无论你是刚开始做SEO&#xff0c;还是已经做了一段时间&#xff0c;你都希望对搜索引擎结果页面&#xff08;SERP&#xff09;的竞争格局有一个清晰的认识&#xff0c;这样你…

LeetCode 590. N 叉树的后序遍历

590. N 叉树的后序遍历 给定一个 n 叉树的根节点 root &#xff0c;返回 其节点值的 后序遍历 。 n 叉树 在输入中按层序遍历进行序列化表示&#xff0c;每组子节点由空值 null 分隔&#xff08;请参见示例&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [1,null,…

关于群晖ARPL界面能出现ip但是使用Synology Assistant搜索不到ip问题 及解决方法

文章引用ing304 频道文章&#xff1a;https://qun.qq.com/qqweb/qunpro/share?_wv3&_wwv128&appChannelshare&inviteCode20jx8dPsU2z&contentID1m4NKs&businessType2&from181174&shareSource5&bizka 前言 当进入该界面后 提示IP无法访问&a…

记录el-select+el-tree复选框,支持模糊查询,懒加载,树父子节点不关联,不全选

需求&#xff1a;一个机构下拉菜单&#xff0c;一个人员下拉菜单&#xff0c;默认带入当前登录用户的机构和人员。机构下拉菜单为两个接口&#xff0c;模糊查询为一个接口不包含懒加载&#xff0c;默认非模糊查询情况下为一个接口&#xff0c;点击节点懒加载。机构下拉菜单数据…

TOP 10 屏幕录制软件工具,可帮您轻松录制视频!

随着越来越多的人远程工作和学习&#xff0c;对可靠、高效的屏幕录制工具的需求变得越来越重要。屏幕录制已成为电子学习、游戏和视频创作的重要组成部分。然而&#xff0c;有这么多可用的屏幕录制工具&#xff0c;选择合适的工具可能具有挑战性。为了帮助您节省搜索时间和精力…

电商新趋势:解析养号的必要性及海外云手机运用攻略

在电商领域&#xff0c;什么最为关键&#xff1f;答案无疑是流量&#xff01;然而&#xff0c;如何以较低成本获取大量流量成为了许多电商从业者头疼的问题。虽然直接投放广告是一种方式&#xff0c;但在内卷的情况下效果越来越难以令人满意&#xff0c;高昂的广告费用也原来越…