面试问答总结之并发编程

news2024/11/15 19:23:47

文章目录

  • 🐒个人主页
  • 🏅JavaEE系列专栏
    • 📖前言:
    • 🎀多线程的优点、缺点
    • 🐕并发编程的核心问题 :不可见性、乱序性、非原子性
      • 🪀不可见性
      • 🪀乱序性
      • 🪀非原子性
      • 🧸JMM(java内存模型)
      • 🏅volatile关键字:保证可见性、禁止指令重排序
      • 🐕CAS机制 (Conpare And Swap 比较并交换)
      • 🐕CAS会产生ABA问题
    • 🏨java中锁的分类
      • 🪀乐观锁、悲观锁
      • 🪀可重入锁
      • 🪀读写锁 ReentrantReadwriteLock
      • 🪀分段锁
      • 🪀自旋锁
      • 🪀共享锁、独占锁
      • 🪀公平锁、非公平锁
        • 🪀针对synchronized锁的状态(底层monitorenter加锁、moniterexit释放锁)
        • 🪀ReentrantLock实现
        • 🏅AQS (AbstractQueuedSynchronizer,抽象同步队列 在JUC并发包下)
        • 🪂集合可以引申过来:ConcurrentHashMap
        • 🪂集合可以引申过来:CopyOnWriteArrayList
      • 🦓辅助类CountDownLatch
    • 🎀线程池
      • 🐕为什么要使用线程池?
      • 🐕你通常是怎么创建线程池的?为啥不使用其他的类Executors?
      • 🏨TreadPoolExecutor构造方法的七个参数
      • 🪂线程池拒绝策略handler
      • 🪂execute()与submit()的区别
      • 🪂关闭线程池shutdown、shutdownNow
    • 🎀ThreadLocal 本地线程变量
      • 🐕什么原因造成ThreadLocal内存泄漏问题?如何解决?

🐒个人主页

🏅JavaEE系列专栏

📖前言:

本篇博客主要总结面试中对线程知识的考察点

🎀多线程的优点、缺点

提高了程序的响应速度,可以多个线程各自完成自己的工作,以提高硬件设备的利用率。
缺点:可能会出现多个线程资源争夺问题,引发死锁。

🐕并发编程的核心问题 :不可见性、乱序性、非原子性

🪀不可见性

一个线程在自己的工作内存中对共享变量的修改,另外一个线程不能立即看到 。

🪀乱序性

为了优化性能,CPU有时候会改变程序中语句的先后顺序,但基本的程序逻辑不受影响。

🪀非原子性

线程切换带来的非原子性问题,A线程执行时,被切换到B线程执行,之后再A线程执行。 如果解决?加锁,如果只是++的话,JUC并发包下的原子类也可以。

🧸JMM(java内存模型)

java内存模型规定了所有的变量都存储在主内存中,每个线程还有自己的工作内存。线程先将主内存中的共享变量复制到自己的工作内存中去,在自己的工作内存中处理数据,再将处理好的结果写入主内存。

🏅volatile关键字:保证可见性、禁止指令重排序

一旦共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,
1.保证了一个线程修改了此变量,其他线程会立即可见(通过《缓存一致性协议》),保证可见性。
2.禁止指令重排序
3.但是volitile不能保证原子性

🐕CAS机制 (Conpare And Swap 比较并交换)

该算法采用自旋的思想,是一种轻量级锁机制。是(不加锁)乐观锁的实现。

线程先从主内存中获取共享变量复制到工作内存中作为预估值,再处理共享变量,得出结果。此时将线程中的预估值与主内存的值进行比较:
1.如果一样,证明没有其他线程干扰,将结果写入主内存。
2.如果不一样,重新获取预估值,重新进行计算结果,再次进行比较…

CAS缺陷:
由于CAS实现的是乐观锁,他会以自旋的方式不断的进行尝试,而不会像synchronized进行线程阻塞,当大量的线程自旋时,容易把CPU跑满。

原子类的实现是volatile +CAS机制。原子类适合在低并发的情况下使用。

🐕CAS会产生ABA问题

就是一个线程预估值为A,来一个线程将内存值改为B,再有一个线程将内存值又改为A。就不确定内存中的值是否其他线程进行干扰过了。
解决方案:
使用有版本号的原子类AtomicABA 根据版本号来解决问题

🏨java中锁的分类

🪀乐观锁、悲观锁

乐观锁: 它乐观认为对同一个数据的并发操作不会产生线程安全问题,不加锁,它会采用自选的方式,不断的尝试更新数据
悲观锁: 它悲观的认为对同一个数据的并发操作会产生线程安全问题,所以需要加锁进行干预
乐观锁适合“读多写少”的场景,悲观锁适合“读少写多”的场景

🪀可重入锁

就是一个加锁的方法可以调用进入另一个加锁的方法中去,两个方法可以共用同一把锁。可以在一定程度上避免死锁。

🪀读写锁 ReentrantReadwriteLock

可以实现读锁、写锁。读写可以使用一个锁实现。
特点:读读不互斥、读写互斥、写写互斥。
加读锁可以防止写,防止脏读。

🪀分段锁

1.8之后,分段锁是一种思想,减小锁的粒度, 将ConcurrentHashMap表每个区间的第一个节点当做锁对象,可以提高并发效率

🪀自旋锁

其实就是线程不断尝试获取锁的方式,不断的循环请求获取锁,直到抢到锁。会占用CPU资源。
例如:
原子类,需要改变主内存中的共享变量而不断尝试。
synchronized 加锁,其他线程不断获取锁,重复一定次数后会陷入阻塞。

🪀共享锁、独占锁

共享锁:多个线程共享一把锁,读写锁中的读锁,都是读操作时多个线程共享
独占锁:即排他锁,只允许一个线程使用该资源,写锁

🪀公平锁、非公平锁

公平锁:是指按照请求锁的顺序来分配锁,具有稳定获取锁的机会
非公平锁:不按照先来后到,谁抢到锁就是谁的。
对于synchronized是一种非公平锁。
而ReentrantLock默认是非公平锁,但是底层可以通过AQS机制来转化为公平锁

🪀针对synchronized锁的状态(底层monitorenter加锁、moniterexit释放锁)

锁的状态是根据对象头中的对象监视器来记录的,对象头Mark Word中记录对象的一些相关信息:hash值、分代年龄、锁的状态、偏向锁的线程id
无锁: 没有任何线程使用锁对象
偏向锁: 当前只有一个线程访问,那么锁对象会在对象头Mark Word中记录该线程id,下次自动获取锁,降低获取锁的难度。
轻量级锁: 当锁状态是偏向锁的时候,又有一个线程访问,此时锁的状态会升级成轻量级锁,其他线程会通过自旋的方式尝试获取锁,不会阻塞线程,提高性能。
重量级锁: 当锁的状态为轻量级锁时,线程自旋获取锁的次数达到一定次数时,锁的状态会升级成重量级锁。 会让自旋次数多的线程进入阻塞状态,以降低CPU的消耗
在这里插入图片描述

在这里插入图片描述

🪀ReentrantLock实现

在这里插入图片描述
。(ReentrantLock 中的内部抽象类sync继承了抽象同步队列AQS,里面定义了lock() 、unlock()方法)
()

🏅AQS (AbstractQueuedSynchronizer,抽象同步队列 在JUC并发包下)

是一个底层同步的实现者,有很多的同步类都继承了它。(ReentrantLock 中的内部类sync继承了它)
AbstractQueuedSynchronizer中有一个volatile修饰的int 类型的变量记录锁的状态(线程是否访问)
AQS里面还有一个内部类Node (双向链表,里面存放线程)
AQS里面获取锁状态方法getState()、修改锁状态方法通过CAS机制进行状态的更新

🪂集合可以引申过来:ConcurrentHashMap

聊到hashMap k-v存储
双列集合,键不能重复,值可以重复。它只适合单线程使用,在多线程情况下会报“并发修改异常”。他的键是由一个哈希表+链表的数据结构实现的。哈希表的初始大小为
16 ,通过对象哈希值%(哈希表长度-1)或者(哈希表长度-1)& 对象哈希值 确定对象在哈希表中的存储位置。如果出现哈希冲突了,通过拉链法解决,形成链表。
当哈希表存储超过0.75时,会进行2倍扩容。当每个节点上链表长度阈值超过8并且哈希表长度>=64,链表将转化为红黑树。当链表长度阈值为6时,红黑树会再次退化成链表。

hashTable它是线程安全的,因为它在每个方法上都加了synchronized锁,哪怕是在读方法上同样也上锁。虽然很安全,但是每个方法只允许一个线程进入,并发访问效率就比较低,适合并发量低的情况下。

在JUC并发包下还有一个ConcurrentHashMap的类。它是线程安全的。它采用了 CAS机制 +synchronized来保证线程安全。它是给节点加锁,降低了锁的粒度。
🏅put()时,先用key计算节点在哈希表中的位置,
它会来判断当前位置上有没有节点(null),如果没有,就使用CAS机制尝试放入。
如果有,就使用第一个节点作为锁对象,用synchronized加锁。这样就可以降低锁的粒度,可以同时有多个线程同时进入put()方法中,提高了并发的效率。但是如果线程在同一个位置操作,那必须还得一个一个来。
concurrentHashMap与hashTable都不允许存储null键,null值。
代码不允许,在源码中if(key==null ||value==null ) 抛出一个空指针异常

为什么不允许存储键为null或值为null?
为了消除歧义,因为如果值为null,不知道是原本Map中没有找到,还是本身就是null.。键同样也是这个道理,会不清楚传进去的键是因为其他原因报错导致的,还是本身就准备传进去一个为null的键。

🪂集合可以引申过来:CopyOnWriteArrayList

单列集合List
ArrayList 底层是数组实现的 ,可以存储重复元素,它是有序的(按添加顺序) , 可以实现动态扩容, 默认大小为10 , 可以进行1.5倍扩容,它是线程不安全的。
Vector 他是线程安全的 ,与ArrayList类似,默认大小为10 ,可以进行2倍扩容,它给每个方法上都加了synchronized锁。虽然它是安全的,但是锁是添加到方法上的,并发访问效率很低,适合并发量不大的情况。

JUC并发包下,CopyOnWriiteArrayList认为读取方法也加锁会造成浪费,因为读是不改变数据的。
它的读操作是不加锁的,并且为了尽可能的提高读的效率。对于改变数据的操作(插入 更新 )会先把原本数据复制到本地副本数组中进行修改,不影响原本数组,所以在写的过程中,也可以读数据。最后将修改好的本地副本数组直接替换为原本数组即可。(写的过程是加锁的,在方法里面使用了ReentrantLock锁,进行数组复制操作…)
它适合读多写少的场景。

CopyOnWriteArraySet底层基于CopyOnWriiteArrayList实现,线程安全的,不能存储重复的数据。

🦓辅助类CountDownLatch

它允许一个线程等待其他线程执行完成后再执行,底层是通过AQS来实现的。刚创建一个CountDownLatch对象时,指定一个初始化state表示需要等 待线程的数量,每当一个线程执行完成后 ,AQS的内部·state数量就减1。

🎀线程池

池:一个容器,将实现创建好的对象放到容器里面,待到使用的时候,不需要在去重新创建对象了,直接从池子里面拿,节省了创建对象的时间。用完不销毁,还放入池子中。

🐕为什么要使用线程池?

以前创建线程的时候,是直接创建线程,使用完后再销毁线程。如果线程比较多(测试5000个数据库连接对象),那么频繁的创建、销毁线程非常占时间,而使用线程池,可以省去频繁创建对象带来的时间开销,直接使用池子里面的,用完不销毁,在放入池子中,速度非常快。

🐕你通常是怎么创建线程池的?为啥不使用其他的类Executors?

通常使用TreadPoolExecutor类创建线程池的,为啥使用这个是因为我参考了《《阿里巴巴 java 开发规范》中推荐使用TreadPoolExecutor来创建线程,而不使用Executors来创建线程。因为TreadPoolExecutor类的构造方法中有七个参数配置,可以准确的配置对象的数量、最大等待的数量、拒绝策略…

🏨TreadPoolExecutor构造方法的七个参数

1.corePoolSize 核心池子中对象的数量
2. maximumPoolSize线程池最大线程对象数量
3. keepAliveTime非核心池对象多长空闲时间后销毁
4. unit:存活时间的单位
5. workQueue:阻塞队列,用于存放等待的线程
6. treadFactory线程工厂,主要用来创建线程
7. handler拒绝策略,表示拒绝处理任务时的策略
在这里插入图片描述

🪂线程池拒绝策略handler

  1. AbortPolicy 抛出异常
  2. CallerRunsPolicy 只要线程池未关闭,如果该任务被拒绝了,则由提交该任务的线程来执行此任务(例如main线程)
  3. DiscardOleddestPolicy 丢弃队列中等待时间最长的任务
  4. DiscardPolicy 直接丢弃任务,不予理会

🪂execute()与submit()的区别

execute( “实现Runnable接口任务” )返回值 void 适合不需要关注返回值的场景,
submit( “实现Callable接口任务” ) 返回值 Future 适合需要关注返回值的场景

🪂关闭线程池shutdown、shutdownNow

shutdown() 等待所有的任务执行完成,关闭线程池,在此期间不接受新的任务。
shutdownNow() 紧急关闭线程池,立即终止正在执行的线程,返回没有执行完任务的列表

🎀ThreadLocal 本地线程变量

是用来给每一个线程提供一个线程副本变量,使得每个线程中的变量是相互隔离的。
在ThreadLocal 底层源码中,它的内部维护了一个ThreadLocalMap内部类,如果为线程创建副本变量,就会判断该线程是否存在ThreadLocalMap
每个线程都有一个ThreadLocalMap属性,以ThreadLocal对象作为键,副本变量作为值。如果该线程已经有了ThreadLocalMap,那么就直接在Map中添加这个 ThreadLocal键 ,副本变量值 即可

🐕什么原因造成ThreadLocal内存泄漏问题?如何解决?

当本地变量不再使用时,由于ThreadLocalMap中这个vaule值还与外界保持着引用关系(强引用),这样一来,垃圾回收器就无法回收这个ThreadLocalMap对象了。
解决办法:
用完后就删除threadLocal.remove();
下次垃圾回收时,就可以回收ThreadLocalMap了。

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

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

相关文章

spring boot3解决跨域的几种方式

⛰️个人主页: 蒾酒 🔥系列专栏:《spring boot实战》 🌊山高路远,行路漫漫,终有归途。 目录 1.前言 2.何为跨域 3.跨域问题出现特征 4.方式一:使用 CrossOrigin 注解 5.方式二:自定义…

百度交出2023年业绩答卷:全力提速AI布局,注入业绩增长新动能

2月28日,百度集团(HK:09888、NASDAQ:BIDU,下称“百度”)发布2023年第四季度及全年财报,交出了一份营收与利润双双跃升的答卷,展现出百度在巩固原有业绩护城河的基础上,投入AI大模型后释放出的巨…

零拷贝技术深入分析

一、零拷贝 在前面的文章“深浅拷贝、COW及零拷贝”中对零拷贝进行过分析,但没有举例子,也没有深入进行展开分析。本文将结合实际的例程对零拷贝进行更深入的分析和说明。 在传统的IO操作中,以文件通过网络传输为例 ,一般会经历以…

【星海随笔】存储硬盘基础信息科普

市场上的磁盘分类有:IDE磁盘(多用于PC机)、SATA磁盘、SAS磁盘、SSD磁盘等 IDE 易于使用与价格低廉,问世后成为最为普及的磁盘接口。 速度慢、速度慢、速度慢。 ATA-7是ATA接口的最后一个版本,也叫ATA133。ATA133接口支…

【C++从0到王者】第四十六站:图的深度优先与广度优先

文章目录 一、图的遍历二、广度优先遍历1.思想2.算法实现3.六度好友 三、深度优先遍历1.思想2.代码实现 四、其他问题 一、图的遍历 对于图而言,我们的遍历一般是遍历顶点,而不是边,因为边的遍历是比较简单的,就是邻接矩阵或者邻接…

ChatGPT学习第四周

📖 学习目标 ChatGPT实践操作 通过实际操作和练习,加深对ChatGPT功能的理解。 项目:创建一个ChatGPT应用案例 设计一个基于ChatGPT的小项目,将理论应用于实践。 ✍️ 学习活动 学习资料 《万字干货!ChatGPT 从零完…

地图可视化绘制 | R-ggplot2 NC地图文件可视化

在推出两期数据分享之后,获取数据的小伙伴们也知道,数据格式都是NetCDF(nc) 格式网格数据,虽然我在推文分享中说明使用Python、R或者GIS类软件都是可以进行 处理和可视化绘制的,但是,还是有小伙伴咨询使用编程软件Pyth…

使用labelimg对YOLO数据进行标注

1.打开pycharm软件 2.在终端安装labelimg:pip install labelimg 3.软件启动后的界面如下: 4.标注格式:标注格式选择YOLO 5.点击Open Dir打开需要标注的路径。 6.然后点击Create RectBox,框出需要标注的物体。 7.在下图对话框中…

vue面试:MVVM、MVC、MVP的区别?

vue面试:MVVM、MVC、MVP的区别? MVVM、MVC、MVP是什么?(1)MVC(2)MVVM(3)MVP MVVM、MVC、MVP是什么? MVC、MVP 和 MVVM 是三种常见的软件架构设计模式&#x…

常用sql语句及其优化

文章目录 介绍常用sql语句1. 数据查询1.1 SELECT 语句1.2 DISTINCT 关键字1.3 WHERE 子句1.4 ORDER BY 子句1.5 LIMIT 关键字 2. 数据更新2.1 INSERT INTO 语句2.2 UPDATE 语句2.3 DELETE FROM 语句 3. 数据管理3.1 CREATE TABLE 语句3.2 ALTER TABLE 语句3.3 DROP TABLE 语句 …

gpt-3.5-turbo与星火认知大模型v3.5回答对比

创建kernel // Create a kernel with OpenAI chat completionKernel kernel Kernel.CreateBuilder().AddOpenAIChatCompletion(modelId:"使用的模型id" ,apiKey: "APIKey").Build();使用讯飞星火认知大模型的话,可以参考我这一篇文章&#xff…

qt5-入门-使用拖动方式创建Dialog

参考: C GUI Programming with Qt 4, Second Edition 本地环境: win10专业版,64位,Qt5.12 目录 实现效果基本流程逐步实操1)创建和初始化子部件2)把子部件放进布局中3)设置tab顺序4&#xff09…

十八:Java8新特性

文章目录 01、Java8概述02、Java8新特性的好处03、并行流与串行流04、Lambda表达式4.1、Lambda表达式使用举例4.2、Lambda表达式语法的使用14.3、Lambda表达式语法的使用2 05、函数式(Functional)接口5.1、函数式接口的介绍5.2、Java内置的函数式接口介绍及使用举例 06、方法引…

Nodejs基于vue的个性化服装衣服穿搭搭配系统sprinboot+django+php

本个性化服装搭配系统主要根据用户数据信息,推荐一些适合的搭配穿搭,同时,用户也可自己扫描上传自身衣物以及输入存放位置,搭配后存储到“我的搭配”中,以便下次挑选,既可以节省搭配时间,也方便…

vue3 构建项目

一.使用vite构建: npm init vitelatest 项目名称 构建的项目模板 进入项目 cd 项目名称 安装项目依赖包 npm install 启动项目 npm run dev 二.使用vue脚手架构建: npm init vuelatest 后续基本差不多

安全防御(第六次作业)

攻击可能只是一个点, 防御需要全方面进行 IAE引擎 DFI和DPI技术 --- 深度检测技术 DPI --- 深度包检测技术 --- 主要针对完整的数据包(数据包分片,分段需要重组) ,之后对 数据包的内容进行识别。(应用层&a…

mock工具whistle使用笔记

1、下载安装地址:关于whistle GitBook 安装完后,用本地的ip:设置的端口就可以反问,端口默认的8899,可以自定义 2、抓包https: (1)打开https (2)下载证书&…

从8.8到9.9,涨价的库迪还能守住牌局吗?

作者 | 辰纹 来源 | 洞见新研社 历经超半年的9.9元活动后,瑞幸不仅牢牢守稳盈利态势,还一举创造了新的神话——中国地区年收入首超星巴克。 根据瑞幸咖啡发布的截至12月31日的2023年第四季度及全年财报。第四季度,瑞幸咖啡净营收为70.6亿元…

Talk|上海交通大学晋嘉睿:序列建模技术在推荐系统中的应用

本期为TechBeat人工智能社区第574期线上Talk。 北京时间2月28日(周三)20:00,上海交通大学博士生—晋嘉睿的Talk已准时在TechBeat人工智能社区开播! 他与大家分享的主题是: “序列建模技术在推荐系统中的应用”,系统地介绍了他们在序列数据的建…

C++数据结构与算法——二叉搜索树的属性

C第二阶段——数据结构和算法,之前学过一点点数据结构,当时是基于Python来学习的,现在基于C查漏补缺,尤其是树的部分。这一部分计划一个月,主要利用代码随想录来学习,刷题使用力扣网站,不定时更…