【Java面试】十七、并发篇(上)

2024-06-09 21:20
文章标签 java 并发 面试 十七

本文主要是介绍【Java面试】十七、并发篇(上),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1、synchronized关键字的底层原理:Monitor
  • 2、synchronized相关
    • 2.1 为什么说synchronized是重量级锁
    • 2.2 synchronized锁升级之偏向锁
    • 2.3 synchronized锁升级之轻量级锁
  • 3、Java内存模型JMM
  • 4、CAS
    • 4.1 CAS流程
    • 4.2 CAS底层实现
  • 5、volatile关键字的理解
    • 5.1 可见性
    • 5.2 禁止指令重排

1、synchronized关键字的底层原理:Monitor

synchronized互斥锁,同一时刻,最多只有一个线程能持有对象锁。用javap拿到synchronized示例代码的字节码信息:

在这里插入图片描述

javap -v SyncTest.class

可以看到synchronized底层自己上锁、解锁(解锁两次是怕出现异常后导致锁不能释放,第二个解锁相当于finally里释放锁)

在这里插入图片描述

Monitor,监视器,由JVM提供,c++实现。每个对象实例都会有一个Monitor对象,Monitor的结构:

在这里插入图片描述

线程Thread1执行到synchronized代码块,如果对象的Monitor对象的Owner属性为null,则抢锁成功,后面其他线程再进来抢锁,就进入EntryList阻塞,直到Thread1执行完释放锁,EntryList里的线程又开始争抢锁(并非先来后到的排队),WaitSet即存调用了wait方法的线程

在这里插入图片描述

2、synchronized相关

2.1 为什么说synchronized是重量级锁

Java应用中的线程是用Thread对象来操作的,JVM负责维护Java线程和操作系统原生线程之间的映射关系。阻塞和唤醒一个线程,都需要CPU参与,而调用硬件资源CPU只能是内核态操作,且Java对象锁通过Monitor实现,Monitor又通过操作系统的互斥量Mutex Lock实现。

因此,加解锁、阻塞线程、唤醒线程等就涉及到了用户态和内核态的频繁切换(两个空间的一些变量的值拷贝),synchronized代码块短的话,可能切换的时间比代码执行时间还长。所以synchronized称为重量级锁。

在这里插入图片描述
鉴于此,JDK6以后,为synchronized引入轻量级锁和偏向锁的概念,避免一下就捅到重量级锁,以尽量减少用户态和内核态的切换次数。

2.2 synchronized锁升级之偏向锁

优化场景:一个锁一直被一个线程持有。如买票时发现线程t3一直在执行卖票的synchronized代码块

在这里插入图片描述
偏向锁会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步,也即偏向锁在资源没有竞争情况下消除了同步语句。

【syncoronized偏向锁】

2.3 synchronized锁升级之轻量级锁

优化场景:两个线程近乎可以错开交替执行, 或者说是有锁竞争, 但竞争不激烈的情况,获取锁的冲突时间极端,本质就是CAS自旋锁,不要直接往重锁走。

【syncoronized轻量锁】

在这里插入图片描述

总之, synchronized的偏向锁和轻量级锁都是为了缓冲, 尽量避免走Monitor + 操作系统的互斥变量来实现加解锁, 以减少用户态和内核态的切换, 实现性能提升

3、Java内存模型JMM

定义了线程工作内存和主内存之间读写操作的规范

在这里插入图片描述

每个线程有自己的工作内存, 每块线程的工作内存之间相互隔离, 操作同一个变量时, 可通过主内存分别save和load

【JMM】

4、CAS

4.1 CAS流程

比较再交换,Compare And Swap,一种乐观锁的思想,在无锁的状态下保证线程操作数据的原子性,CAS广泛用于AQS框架、AtomicXXX类

  • 当前工作内存的值V
  • 旧的预期值A
  • 即将更新,写回主内存的值B

示意代码:

在这里插入图片描述

t1线程从主内存读到i=5,+1后准备把6写回主内存,此时,比较期望值5和内存中的实际i值,若相等,则乐观的认为自己运算的期间没有其他线程修改i,就将i写回主内存。反之,比如主内存i=6,那t1就再来一次,i=6,i+1,期望6,此时如果主内存i=6,则写回成功,反之继续自旋。

在这里插入图片描述

CAS的优势是没有加锁,线程不会陷入阻塞,效率高,反之,如果竞争激烈,频繁失败自旋重试,效率低且消耗CPU

4.2 CAS底层实现

底层通过Unsafe类来直接调用操作系统底层的CAS指令,来保证原子性,CAS对应在底层是CPU的一条原子指令cmpxchg

在这里插入图片描述

最后,CAS体现乐观锁的思想是,不担心自己操作期间别的线程修改了共享变量,如果被改了就吃点亏再重执行一次,反正我从主内存读数据、在工作内存读数据、写回主内存三步不可再分,有原子性保证,撑死多试几次才能修改数据成功

反之,synchronized则是悲观锁,自己操作时要防着其他线程改共享变量,上个锁,我改完解锁之后,别的线程才有机会

5、volatile关键字的理解

volatile修饰的变量 :

  • 保证线程间的可见性
  • 禁止进行指令重排序

5.1 可见性

volatile的可见性,即保证不同线程对某一个变量一旦完成更改,其他线程立即可见,因为会从线程的工作内存立马刷到主内存

在这里插入图片描述

执行结果:

在这里插入图片描述

结果分析:三个线程操作一个共享变量stop,线程1改了stop的值,一会儿线程2就读到了这个变化,但线程3却一直在循环,这是因为JVM的即时编译器JIT对一直执行的热点代码做了优化:

在这里插入图片描述

可加JVM参数-Xint禁用掉即时编译器,但得不偿失,可给共享变量加stop,JIT不会对volatile变量做优化且volatile变量会立即刷回主内存

static volatile boolean stop = true;

重新运行,显示循环16038233次后,读到stop变量变为true了:

在这里插入图片描述

5.2 禁止指令重排

看个案例:用@Actor注解保证方法内u的代码在同一个线程下执行

在这里插入图片描述

以上代码,并发测试下出现1 ,0 的结果,说明发生了重排序:

在这里插入图片描述
用volatile修饰变量,在读写共享变量时加入屏障,阻止其他读写操作越过屏障,以阻止指令重排序

//改为这样
int x;
volatile int y;

此时,并发测试下不再有1 ,0 的结果,屏障如下:

在这里插入图片描述
如果给x加volatile,则还是有1, 0 的结果

//改为这样
volatile int x;
int y;

因为此时的插入的屏障位置如下:

在这里插入图片描述
上面,volatile修饰x还是y,这是一个volatile的使用技巧问题:

  • 写变量让 volatile 修饰的变量的在代码最后位置
  • 读变量让 volatile 修饰的变量的在代码最开始位置

详见:volatile读写屏障

这篇关于【Java面试】十七、并发篇(上)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1046325

相关文章

Java五子棋之坐标校正

上篇针对了Java项目中的解构思维,在这篇内容中我们不妨从整体项目中拆解拿出一个非常重要的五子棋逻辑实现:坐标校正,我们如何使漫无目的鼠标点击变得有序化和可控化呢? 目录 一、从鼠标监听到获取坐标 1.MouseListener和MouseAdapter 2.mousePressed方法 二、坐标校正的具体实现方法 1.关于fillOval方法 2.坐标获取 3.坐标转换 4.坐

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

java8的新特性之一(Java Lambda表达式)

1:Java8的新特性 Lambda 表达式: 允许以更简洁的方式表示匿名函数(或称为闭包)。可以将Lambda表达式作为参数传递给方法或赋值给函数式接口类型的变量。 Stream API: 提供了一种处理集合数据的流式处理方式,支持函数式编程风格。 允许以声明性方式处理数据集合(如List、Set等)。提供了一系列操作,如map、filter、reduce等,以支持复杂的查询和转

Java面试八股之怎么通过Java程序判断JVM是32位还是64位

怎么通过Java程序判断JVM是32位还是64位 可以通过Java程序内部检查系统属性来判断当前运行的JVM是32位还是64位。以下是一个简单的方法: public class JvmBitCheck {public static void main(String[] args) {String arch = System.getProperty("os.arch");String dataM

详细分析Springmvc中的@ModelAttribute基本知识(附Demo)

目录 前言1. 注解用法1.1 方法参数1.2 方法1.3 类 2. 注解场景2.1 表单参数2.2 AJAX请求2.3 文件上传 3. 实战4. 总结 前言 将请求参数绑定到模型对象上,或者在请求处理之前添加模型属性 可以在方法参数、方法或者类上使用 一般适用这几种场景: 表单处理:通过 @ModelAttribute 将表单数据绑定到模型对象上预处理逻辑:在请求处理之前

eclipse运行springboot项目,找不到主类

解决办法尝试了很多种,下载sts压缩包行不通。最后解决办法如图: help--->Eclipse Marketplace--->Popular--->找到Spring Tools 3---->Installed。

JAVA读取MongoDB中的二进制图片并显示在页面上

1:Jsp页面: <td><img src="${ctx}/mongoImg/show"></td> 2:xml配置: <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001

Java面试题:通过实例说明内连接、左外连接和右外连接的区别

在 SQL 中,连接(JOIN)用于在多个表之间组合行。最常用的连接类型是内连接(INNER JOIN)、左外连接(LEFT OUTER JOIN)和右外连接(RIGHT OUTER JOIN)。它们的主要区别在于它们如何处理表之间的匹配和不匹配行。下面是每种连接的详细说明和示例。 表示例 假设有两个表:Customers 和 Orders。 Customers CustomerIDCus

22.手绘Spring DI运行时序图

1.依赖注入发生的时间 当Spring loC容器完成了 Bean定义资源的定位、载入和解析注册以后,loC容器中已经管理类Bean 定义的相关数据,但是此时loC容器还没有对所管理的Bean进行依赖注入,依赖注入在以下两种情况 发生: 、用户第一次调用getBean()方法时,loC容器触发依赖注入。 、当用户在配置文件中将<bean>元素配置了 lazy-init二false属性,即让