多线程进阶

多线程常见面试题

文章目录

  • 多线程常见面试题
    • 1. 常见的锁策略
      • 1.1乐观锁&悲观锁
      • 1.2 轻量级锁&重量级锁
      • 1.3 自旋锁&挂起等待锁
      • 1.4 读写锁&普通互斥锁
      • 1.5 公平锁&非公平锁
      • 1.6可重入锁&不可重入锁
    • 2. CAS
    • 3. Sychronized原理
      • 3.1 锁升级
      • 3.2 锁消除
      • 3.3 锁粗化
    • 4. JUC
      • 4.1 Callable接口(创建线程的方式)
      • 4.2 创建线程的几种方式
      • 4.3 ReentrantLock
    • 5. JUC工具类
      • 5.1 Semaphore-信号量
      • 5.2 CountDownLatch
      • 5.3 CyclicBarrier - 循环栅栏
    • 6.线程安全的集合类
    • 7.多线程使用队列
    • 8.多线程环境使用哈希表
    • 9.死锁

1. 常见的锁策略

1.1乐观锁&悲观锁

在加锁的态度的角度去执行加锁逻辑

乐观锁:在获取锁的时候预期这个锁竞争不太激烈,那么就可以先不加锁,或者少加锁(有真实的竞争再来加锁)

悲观锁:在获取锁的时候预期这个锁竞争非常激烈,那么就必须先加锁再执行任务

1.2 轻量级锁&重量级锁

站在加锁过程角度的描述

轻量级锁:加锁的过程比较简单,用到的资源比较少,典型就是用户态的一些加锁操作(在Java层面就可以完成加锁)

重量级锁:加锁的过程比较复杂,用到的资源比较多,典型就是内核态的一些操作

乐观锁是能不加就不加,从而导致他干的活就比较少了,那么他消耗的资源就比较少了, 从而可以说乐观锁是一个轻量级锁

悲观锁是不管怎么样都先把锁加上,从而导致他干的活多了,那么消耗的资源就比较多了,可以说被悲观锁也是一种重量级锁

1.3 自旋锁&挂起等待锁

自旋锁:不停的检查锁是否被释放,如果一旦锁被释放掉那么就直接获取锁资源

自旋锁的优点:

  1. 他是一个纯用户态的操作,比较轻量
  2. 锁一旦被释放就可以马上知道

还有一些缺点:

  1. 不停的循环比较浪费系统的资源

    可以通过控制自旋的次数来提高效率,并且避免系统资源的过度浪费

挂起等待锁:不主动询问锁资源,而是让系统调度去竞争锁资源

  1. 通过阻塞与就绪状态的切换来获取锁资源
  2. 如果锁一旦释放,没有办法立马知道
  3. 是通过系统内核来处理的

自旋锁是一种典型的轻量级锁的具体实现

挂起等待锁是一种典型的重量级锁的具体实现

1.4 读写锁&普通互斥锁

读写锁:在锁中标识读或写,在竞争时根据这个标识来判断是否参与竞争

读的时候加读锁(共享锁),多个锁可以共存,同时多个读锁是互不影响的

写的时候加写锁(排他锁),只能有一个写锁在执行任务,和别的锁是冲突的

写锁和写锁不能共存

写锁和读锁不能共存

读锁和读锁可以共存

为什么要用读写锁

在我们的程序中可能会出现激烈的锁竞争,但是获取锁之后的操作,有可能是大量的读操作,读操作又不涉及修改

所以所有的读都可以并发执行,只有读的时候不让别的线程来修改就可以了

如果一个操作是读操作,当前也是读锁,那么就直接读了,不产生锁竞争,从而节省了资源

如果一个操作是写操作,这个时候就让他等待

针对高并发读的业务场景中,锁竞争就会大大降低,从而提交程序的运行效率

互斥锁

有竞争关系,只能一个线程释放锁之后,别的线程再来抢

1.5 公平锁&非公平锁

公平锁:先来后到,先排队的线程先获取到锁,后排队的后获取到锁

非公平锁:谁先抢到就是谁的

在Java JUC中有一个类实现了公平锁

synchronized是一个非公平锁

1.6可重入锁&不可重入锁

可重入锁:对一把锁可以连续加锁多次,而不造成死锁

不可重入锁:对一把锁连续加锁多次,造成死锁

对比sychronized

乐观锁&悲观锁即是乐观锁也是悲观锁
轻量级锁&重量级锁即是轻量级锁也是重量级锁
自旋锁&挂起等待锁即是自旋锁也是挂起等待锁
读写锁&普通互斥锁是互斥锁
公平锁&非公平锁是非公平锁
可重入锁&不可重入锁是可重入锁

当锁竞争不激烈时,是一个乐观锁,轻量级锁,自旋锁

如果竞争不激烈这个状态就一直保证

如果锁竞争激烈的时候,就会升级为悲观锁,重量级锁,挂起等待锁

2. CAS

compare and swap 比较并交换

首先比较当前变量值与某个值期望的值是否相同,如果相同则用一个新的值变量内存中的值

boolean CAS(address,exceptValue,swapValue){if(&address==exceptValue){&address=swapValue;return true;}return false;
}//核心逻辑
if(value==exceptValue){value=oldValue;
}
  1. 用一个预期值去和内存中的值做比较
  2. 如果预期值与内存中的值相等,那么就用新值更新内存中的值
  3. 如果预期值与内存中的值不相等,那么就不做任何操作

画图演示

1.两个线程读取value的值到oldvalue

image-20230909134802205

  1. 线程1执行CAS操作,由于oldvale与value值相等。直接对value赋值

    image-20230909141143120

  2. 线程2再执行CAS操作,第一次CAS时发现oldCValue和value不相等,不能进行赋值。因此需要进入循环(自旋),在循环里重新读取value的值赋给oldValue

    image-20230909141415610

  3. 线程2接下来第二次执行CAS,此时oldValue与value相同,于是直接执行赋值操作

    image-20230909141457567

CAS操作直接修改的是内存中的值,每次都会去读,去比较去修改指定内存地址的值,从而保证了原子性的操作

CAS中ABA的问题

3. Sychronized原理

Sychronize本身会对锁做一些智能的优化,在程序不同的运行时期,适应不同的锁策略

3.1 锁升级

无锁——>偏向锁(JVM启动4秒后,创建的锁对象才会偏向状态)——>轻量级锁——>重量级锁

无锁:没有锁竞争时

偏向锁:只是给锁对象中加入了一个标签,并没有真正的去加锁

轻量级锁:通过自旋锁实现用户态的操作

重量级锁:内核态的加锁操作,调用的是CPU加锁指令

3.2 锁消除

sychronized的一种优化策略

sychronized是程序员自己手动加的获取锁的逻辑,什么时候加,加在哪个代码块,JVM管不了,但是在编译和运行的时候,JVM可以知道程序是读变量,还是写变量

如果程序员对所有的读操作都加了sychronized关键字,但是又没有写操作,那么这是JVM就认为这个锁是多余的,那么sychronized就不会真正的去加锁了,这个现象称为锁消除

在多线程状态下,多个线程对同一变量进行修改才会又线程安全问题,多个线程读一个变量没有线程安全问题

3.3 锁粗化

锁的粒度,也就是锁的范围,也就是sychronized包裹代码的多少,包的越多粒度就越粗,包的越少粒度就越细,对于多个连续的任务,如果每个任务都加一把锁,这个过程就会产生频繁的锁竞争,JVM就会把锁的范围增大到整个任务的开始与结束,减少锁竞争的次数来提高效率。

4. JUC

4.1 Callable接口(创建线程的方式)

描述线程要执行的任务

Callable和Runnable有什么区别

  1. Callable要实现call()且有返回值,Runnable方法要实现run(),没有返回值
  2. Callable的call()方法可以抛出异常,Runnable的Run()方法不能抛出异常(业务异常)
  3. Callable要搭配FutureTask一起使用,通过futureTask.get()来获取call()的返回值
  4. 两者都是描述线程任务的接口

4.2 创建线程的几种方式

创建线程的几种方式

  1. 继承Tread类,实现run()方法
  2. 实现Runnable接口,实现run()方法
  3. 实现Callable接口,实现call()方法
  4. 通过线程池,提交任务

由于Runnable和Callable都是函数式接口,我们可以通过Lambda表达式的方式简化写法,也可以通过匿名内部类的方式来简化写法

4.3 ReentrantLock

JUC中重要的一个技术点

lock()加锁

unLock()解锁

tryLock()尝试解锁

sychrinized与ReentrantLock区别

  1. sychronized退出代码块就自动释放锁,ReentrantLock必须手动释放锁,注意要是有try/finallu处理加锁释放锁的代码
  2. sychrinized是非公平锁,ReentrantLock即是非公平锁也是公平锁
  3. ReentrantLock可以根据不同的条件去进行休眠和唤醒
  4. sychronized在申请锁失败时,会一直等待锁资源,而ReetrantLock可以通过tryLock的方式等待一段时间就放弃
  5. sychronized是JVM的一个对锁的实现,最终调用CPU加锁指令,而ReetrantLock是Java层面的JUC包中的一组实现类

5. JUC工具类

5.1 Semaphore-信号量

申请资源的操作称为P操作,资源数量减一,当减到0的时候其他的线程就要等待

释放资源的操作称为V操作,资源数量就要加一

信号量本质上就是要维护资源的数量

5.2 CountDownLatch

等待所有线程全都完成任务后才执行后面的操作

5.3 CyclicBarrier - 循环栅栏

CountDownLatch的进级版,可以实现线程间的相互等待,计数重置

6.线程安全的集合类

Vector,Stack,HashTable

如何在多线程环境下保证集合类的线程安全

  1. 手动加锁,sychronized包裹代码块,ReentrantLock 加锁解锁
  2. 使用工具类Collections.synchronizedList(array)(不常用)
  3. JUC 提供了集合类CopyOnWriteArraylist

7.多线程使用队列

  1. ArrayBlockingQueue基于数组实现的队列
  2. LinkedBlockingQueue基于链表实现的队列
  3. PriorityBlockingQueue 基于堆实现的优先级队列
  4. TransferQueue最多只包含一个元素的阻塞队列

8.多线程环境使用哈希表

HashTable JDK1.0提供 线程安全不推荐使用

HashMap线程不安全

面试题

  1. 多线程环境下,用哪个类来保证Map的线程安全

    JUC包下的ConcurrentHashMap

  2. ConcurrentHashMap与HashMap/HashTable 的区别

    1. HashMap是线程不安全的
    2. HashTable是线程安全的,但是不推荐使用,因为所有的操作都加锁了
    3. ConcurrentHashMap的锁粒度比较小,不是对整个Hash表加锁,而是对每一个数据的下标进行加锁
    4. ConsurrentHashMap对写操作加锁,对读操作不加锁
    5. 对于共享变量大量运行了volatile关键字去修饰
    6. ConcurrentHashMap对扩容进行了优化

    扩容

    HashMap和HashTable在扩容时,重新创建一个容量是当前容量2倍的新数组,把所有的元素全部重新hash到新数组里去,是一个完整的操作,效率并不是很高

    ConcurrentHashMap,创建一个新数组容量是原来的2倍,原数组与新数组同时存在一段时间,每次调用ConcurrentHashMap方法的时候,都去搬运一部分元素到新数组中,当原数组中的数据搬运完后,原数组就可以删除了,只使用新数组就可以了

    当两个数组同时存在的时候,查询数据就需要在两个Map中同时查询

    删除时在两个Map中查找,找到后删除

    写入时只往新的Map中去写

    典型的以空间换时间的做法,浪费了一些存储空间,但提高了运行效率

9.死锁

在多线程环境中遇到的最严重的问题之一,线程在获取锁资源的时候,由于获取不到导致线程卡死(阻塞),不执行了

造成死锁的原因

  1. 资源互斥:线程1拿到了锁A,线程2不能同时得到锁
  2. 不可抢占:获取到锁的线程,除非自己主动释放锁,别的线程不能从他的手里抢过来
  3. 保持与请求:线程1已经获得了锁A,还要在这个基础再去获取锁B
  4. 循环等待:线程1等待线程2释放锁,线程2等待线程3释放锁,线程3等待线程1释放锁……

以上四条是造成死锁的必要条件,必须同时满足

如何解决死锁

  1. 互斥访问:锁的基本特性,不能打破
  2. 不可抢占:锁的基本特性,不能打破
  3. 保持与请求:和代码实现或是设计的角度来说是可以改变保持与请求的顺序,也就是获取锁的顺序
  4. 循环等待:最有可能也是最常见的解决死锁的策略就是打破循环等待

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

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

相关文章

支持多校 微信课表小程序源码 排课小程序源码 支持导入课表 情侣课表 背景设置

练手Lab课程表小程序源码是一个基于thinkphp系统进行开发的前后端分离系统。 源码功能介绍 1、情侣功能 2、情侣间留言 3、情侣间互相设置课程表背景 4、自己日、周课程表背景设置 5、教务系统课程表导入 6、导入别人分享的课表 7、导入别人分享的单课 8、多校支持 9…

MySQL - 为什么官方建议使用自增长主键作为索引

性能提升:自增长主键通常是整数类型,它们在数据库中的存储和索引效率非常高。这使得查询、插入和更新操作更加高效,特别是在大型数据表中。整数类型的比较和排序速度通常更快。减小索引尺寸:自增长主键的值按顺序递增,…

页面html结构导出为word或pdf

一、使用场景和原理 需要将当前页面(一般详情页面)或者dom容器中的内容保存/截图,并且导出为word或者pdf 导出word:获取dom结构直接转化为word导出 导出pdf:用canvas生成当前页面或者dom范围的快照,参考截图功能,然后将生成的canvas转为pdf内…

BootLoader为什么要分阶段?

BootLoader(引导加载程序)分阶段的设计主要是为了实现系统的启动和引导过程的可靠性、可维护性和灵活性。这种分阶段的设计允许引导加载程序执行不同的任务,并在不同的环境下工作。以下是引导加载程序分阶段的主要原因: 1. 启动环…

面试题收集——Java基础部分(一)

1、一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制?   可以有多个类,但只能有一个public的类,并且public的类名必须与文件名相一致。 2、Java有没有goto?   java中的保留字…

【c#】关于web api发布

c#关于web api发布 发布方式区别和原理发布步骤1、文件夹发布2、WEB服务器发布3、详细Web Deploy部署1、在IIS服务器上安装WebDeploy2、添加Web管理工具3、运行下面2个服务4、打开IIS管理界面,添加网站,添加完成后不要运行网站,因为现在还没有网站文件。5、在添加的网站上右…

MySQL - 对字符串字段创建索引

在数据库中,对字符串字段创建索引可以加速字符串字段的查询: 直接创建完整索引:这是最简单的方式,直接对整个字符串字段创建索引。这种方式占用的空间较大,但查询性能通常较好,特别是在精确匹配的情况下。…

docker容器怎么设置开机启动

docker容器怎么设置开机启动 docker服务器、以及容器设置自动启动 回到顶部 一、docker服务设置自动启动 说明:适用于yum安装的各种服务 查看已启动的服务 systemctl list-units --typeservice 查看是否设置开机启动 systemctl list-unit-files | grep enable 设…

AIR101 LuatOS LVGL 显示多个标签例程

屏幕资料 AIR101与屏幕连接 PC端仿真环境合宙官方PC端版本环境搭建教程 PC电脑仿真 -- sys库是标配 _G.sys require("sys") sys.taskInit(function()local cnt0lvgl.init(480,320)--lvgl初始化local cont lvgl.cont_create(nil, nil);-- lvgl.cont_set_fit(cont, …

MFC Windows 程序设计[332]之十进制转十六进制编辑框(附源码)

MFC Windows 程序设计[332]之十进制转十六进制编辑框 程序之美前言主体运行效果核心代码逻辑分析结束语程序之美 前言 MFC是微软公司提供的一个类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包…

Centos8 降低gcc版本至gcc-7.3

1 首先卸载系统中的gcc sudo yum remove gcc 2 重新安装gcc-7.3 sudo dnf group install “Development Tools” 然后再次卸载gcc sudo yum remove gcc 然后发现centos-release-scl-rh已经安装了 sudo yum install centos-release-scl-rh yum -y install devtoolset-7-gcc dev…

跨界技术:SOCKS5代理在电商、爬虫与游戏领域的应用

随着技术的日益发展,各种工具和技术手段被广泛应用于不同的领域。其中,SOCKS5代理、跨界电商、爬虫技术、出海策略以及游戏产业都成为了当下最热门的话题。本文将探讨这些关键技术如何相互融合,为企业和个人带来更多的机会和挑战。 1. SOCKS…

spring中基础核心接口总结

理解这几个接口,及其实现类就可以快速了解spring,具体的用法参考其他spring资料 1.BeanFactory最基础最核心的接口 重要的实现类有:XmlBeanFactory,以及ApplicationContext接口下的类 2.Resource接口,可以通用地访问文件资源 1)ClassPathResource:读取…

不希望你的数据在云中?关闭iPhone或Mac上的iCloud

​如果你不想使用iCloud,可以很容易地从设备设置中选择退出并关闭它。当你禁用iCloud时,它会删除该设备对iCloud的访问,但不会删除苹果服务器上的任何数据。我们将在本文末尾向你展示如何做到这一点。 注销iCloud并完全禁用它 如果你根本不…

Selenium自动访问Firefox和Chrome并实现搜索截图

一. Selenium自动登录 代码如下所示: from selenium import webdriver from selenium.webdriver.common.keys import Keys import time #模拟登陆163邮箱 driver = webdriver.Firefox() driver.get("http://mail.163.com/") #用户名 密码 elem_user = …

HDR图像处理软件 Photomatix Pro mac中文版新增功能

Photomatix Pro mac是一款专业的HDR合成软件,可以将不同曝光的多张照片合成为一张照片,而保留更多的细节。并且合成时可以帮助去除照片中的鬼影。Photomatix Pro提供两种类型的过程来增加动态范围,一个过程称为HDR色调映射,另一个…

hive字段关键字问题处理

最近在xxl_job部署shell调度任务时,发现在编写Hql时,对一些使用关键字命名的字段无法解析,按开发规范,字段命名不应该有关键字,但是数据来源是第三方,无法修改,需要通过flume对从kafka的数据到hdfs上,数据是json格式,所以需要对关…

Python 文本语种检测模型:cld2-cffi

Python 文本语种检测模型:cld2-cffi 安装 :pip install cld2-cffi 代码 import cld2t [A accs aux chiens et aux frontaux qui lui ont t il peut consulter et modifier ses collections et exporter Cet article concerne le pays europen aujour…

YOLOv5算法改进(20)— 如何去写YOLOv5相关的论文(包括论文阅读+规律总结+写作方法)

前言:Hello大家好,我是小哥谈。最近一直在阅读关于YOLOv5的相关论文,读着读着我发现一条可以发论文的规律,特此简单总结一下,希望能够对同学们有所启迪!🌈 前期回顾: YOLOv5算法改进(1)— 如何去改进YOLOv5算法

kafka入门03——简单实战

目录 安装Java 安装Zookeeper 安装Kafka 生产与消费 主要是记录下Kafka的安装配置过程,前置条件需要安装jdk和zookeeper。 安装Java 1.Oracle官网下载对应jdk安装包 官网地址:Java Downloads | Oracle 好人分享了下载需要的oracle账号&#xff0c…