一篇文章带你搞懂多线程面试相关的一些问题

目录

1.Callable接口

1.1使用Callable接口来创建线程

1.1相关面试题:

介绍下 Callable 是什么

2.JUC常见的类(java.util,concurrent)

2.1ReentrantLock

 ReentrantLock和sychronized的区别

3.信号量

4.CountDownLatch

5.线程安全的集合类

5.1多线程下使用ArrayList

5.2多线程下使用队列

5.3多线程环境使用哈希表

5.3.1 Hashtable

5.3.2 CoucurrentHashMap

5.4一些面试题

5.4.1.ConcurrentHashMap的读是否要加锁,为什么?

5.4.2ConcurrentHashMap在jdk1.8做了哪些优化?

5.4.3Hashtable和HashMap、ConcurrentHashMap 之间的区别?


1.Callable接口

1.1使用Callable接口来创建线程

  我们在创建一个线程的时候有以下几种方法:
1.继承Thread类

2.实现runnable接口

3.使用lambda表达式

4 基于callable

5基于线程池

在这里面 Runnable关注的是过程而不是结果,它并没有返回值,所以如果别的地方要用到它的返回值,就得使用别的办法 如:

 public static int b = 0;public static void main(String[] args) {Runnable runnable = new Runnable() {@Overridepublic void run() {int a = 0;for (int i = 1; i <=10 ; i++) {a+=i;}b = a;}};runnable.run();System.out.println(b);}

我们希望计算从1~10的累加,但是Runnable接口并没有返回值,所以我们得用一个成员变量来赋值才行,这样写固然可以,但是不够优nia

我们。可以用别的方法来创建一个带返回值的线程:

 public static void main(String[] args) throws Exception {
//创建的时候可以指定返回值类型Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int a = 0;for (int i = 1; i <=10 ; i++) {a+=i;}return a;}};FutureTask<Integer> futureTask = new FutureTask<>(callable); //使用FutureTask类来接收callable对象Thread t = new Thread(futureTask); //将futureTask对象传给Thread里面t.start();int n = futureTask.get();System.out.println(n);}

理解Callable

Callable 和 Runnable 相对, 都是描述一个 "任务". Callable 描述的是带有返回值的任务,
Runnable 描述的是不带返回值的任务.
Callable 通常需要搭配 FutureTask 来使用. FutureTask 用来保存 Callable 的返回结果. 因为
Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定.
FutureTask 就可以负责这个等待结果出来的工作

1.1相关面试题:

介绍下 Callable 是什么


Callable 是一个 interface . 相当于把线程封装了一个 "返回值". 方便程序猿借助多线程的方式计算
结果.
Callable 和 Runnable 相对, 都是描述一个 "任务". Callable 描述的是带有返回值的任务,
Runnable 描述的是不带返回值的任务.

Callable 通常需要搭配 FutureTask 来使用. FutureTask 用来保存 Callable 的返回结果. 因为
Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定.
FutureTask 就可以负责这个等待结果出来的工作.
 

2.JUC常见的类(java.util,concurrent)

2.1ReentrantLock

可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全.
ReentrantLock 也是可重入锁. "Reentrant" 这个单词的原意就是 "可重入
 

ReentrantLock 的用法:
lock(): 加锁, 如果获取不到锁就死等.
trylock(超时时间): 加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁.
unlock(): 解锁
 

 public static void main(String[] args) {ReentrantLock lock = new ReentrantLock();lock.lock();try {//主要的逻辑代码}finally {lock.unlock();//注意要释放锁}}

面试题:

 ReentrantLock和sychronized的区别

1.synchronized 是一个关键字, 是 JVM 内部实现的(大概率是基于 C++ 实现). ReentrantLock 是标准库的一个类, 在 JVM 外实现的(基于 Java 实现).
2。synchronized 使用时不需要手动释放锁. ReentrantLock 使用时需要手动释放. 使用起来更灵活,
但是也容易遗漏 unlock.
3.synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的方式等待一段时间就
放弃.
4.synchronized 是非公平锁, ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个 true 开启公平锁模式
5.更强大的唤醒机制. synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是一
个随机等待的线程. ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指
定的线程.


如何选择使用哪个锁?
锁竞争不激烈的时候, 使用 synchronized, 效率更高, 自动释放更方便.
锁竞争激烈的时候, 使用 ReentrantLock, 搭配 trylock 更灵活控制加锁的行为, 而不是死等.
如果需要使用公平锁, 使用 ReentrantLock.
 

3.信号量

  信号量, 用来表示 "可用资源的个数". 本质上就是一个计数器
用一个通俗易懂的例子来理解信号量:

信号量就是,我去饭店吃饭,饭店里面的座位数,要是座位被坐满了。那么我就进不去了,一旦有人离开了座位(v操作)那么就是释放资源,这个时候。我就可以进去了,而不是在门口阻塞等待。

而我进去了以后坐上座位,属于申请资源。(p操作),可用资源数(座位个数)就减少了。一旦到了0,那么就不能在被申请了。

Java中 使用Semphore类来封装了信号量机制

代码实例:

首先我们可以不用信号量 用两个线程来分别对count++ 

public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 5000; i++) {count++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 5000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}

 得到结果:

可以看出和我们预期的结果是不一样的,因为两个抢占式运行,并且count++这个操作并非是原子性的,所以会这样。

接下来我们引用信号量机制 ,并且给它的初始值设为1

public static void main(String[] args) throws InterruptedException {Semaphore semaphore =new Semaphore(1);Thread t1 = new Thread(()->{for (int i = 0; i < 5000; i++) {try {semaphore.acquire();} catch (InterruptedException e) {throw new RuntimeException(e);}count++;semaphore.release();}});Thread t2 = new Thread(()->{for (int i = 0; i < 5000; i++) {try {semaphore.acquire();} catch (InterruptedException e) {throw new RuntimeException(e);}count++;semaphore.release();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);

无论我们执行多少次 结果都是我们预期的。

4.CountDownLatch


  同时等待 N 个任务执行结束.
好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩
构造 CountDownLatch 实例, 初始化 10 表示有 10 个任务需要完成.
每个任务执行完毕, 都调用 latch.countDown() . 在 CountDownLatch 内部的计数器同时自减.
主线程中使用 latch.await(); 阻塞等待所有任务执行完毕. 相当于计数器为 0 了.
 

        public static void main(String[] args) throws Exception {CountDownLatch latch = new CountDownLatch(10);Runnable r = new Runnable() {@Overridepublic void run() {try {Thread.sleep((long) (Math.random() * 10000));latch.countDown();} catch (Exception e) {e.printStackTrace();}}};for (int i = 0; i < 10; i++) {new Thread(r).start();}// 必须等到 10 人全部回来latch.await();System.out.println("比赛结束");}

5.线程安全的集合类

原来的集合类, 大部分都不是线程安全的.
Vector, Stack, HashTable, 是线程安全的(不建议用), 其他的集合类不是线程安全的

5.1多线程下使用ArrayList

1.使用synchronized或者ReentrantLock

2.Collections.synchronizedList(new ArrayList);
synchronizedList 是标准库提供的一个基于 synchronized 进行线程同步的 List.
synchronizedList 的关键操作上都带有 synchronized

3.使用 CopyOnWriteArrayList
CopyOnWrite容器即写时复制的容器。
当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,
复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会
添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
优点:
在读多写少的场景下, 性能很高, 不需要加锁竞争.
缺点:

1. 占用内存较多.
2. 新写的数据不能被第一时间读取到
 

5.2多线程下使用队列

1.ArrayBlockingQueue 基于数组实现的阻塞队列 是线程安全的

2,LinkedBlockingQueue 基于链表实现的阻塞队列

3.PriorityBlockingQueue 基于堆实现的 带有优先级的阻塞队列

4.TransferQueue 最多只包含一个元素的阻塞队列

5.3多线程环境使用哈希表

HashMap并不是线程安全的

在多线程下可以使用:

Hashtable

CoucurrentHashMap

5.3.1 Hashtable

把关键方法都加上了synchornized关键字

这相当于直接针对 Hashtable 对象本身加锁.
如果多线程访问同一个 Hashtable 就会直接造成锁冲突.
size 属性也是通过 synchronized 来控制同步, 也是比较慢的.
一旦触发扩容, 就由该线程完成整个扩容过程. 这个过程会涉及到大量的元素拷贝, 效率会非常低
整个哈希表就只有一把锁

5.3.2 CoucurrentHashMap

相比于 Hashtable 做出了一系列的改进和优化. 以 Java1.8 为例
读操作没有加锁(但是使用了 volatile 保证从内存读取结果), 只对写操作进行加锁. 加锁的方式仍然
是是用 synchronized, 但是不是锁整个对象, 而是 "锁桶" (用每个链表的头结点作为锁对象), 大大降
低了锁冲突的概率.
充分利用 CAS 特性. 比如 size 属性通过 CAS 来更新. 避免出现重量级锁的情况.
优化了扩容方式: 化整为零
发现需要扩容的线程, 只需要创建一个新的数组, 同时只搬几个元素过去.
扩容期间, 新老数组同时存在.
后续每个来操作 ConcurrentHashMap 的线程, 都会参与搬家的过程. 每个操作负责搬运一小
部分元素.
搬完最后一个元素再把老数组删掉.
这个期间, 插入只往新数组加.
这个期间, 查找需要同时查新数组和老数组
 

每个链表都有一把单独的锁

5.4一些面试题

5.4.1.ConcurrentHashMap的读是否要加锁,为什么?

不需要锁,目的是为了进一步降低锁冲突的概率,为了保证读到刚修改的关键字,可以使用volatile关键字

5.4.2ConcurrentHashMap在jdk1.8做了哪些优化?

取消了分段锁,直接给每个哈希桶都分配了一把锁。

将原来数组+链表的方式改进成了数组+链表/红黑树的形式。当数组长度大于64,链表长度大于8 到时候就会转变为红黑树

5.4.3Hashtable和HashMap、ConcurrentHashMap 之间的区别?

首先,HashMap并不是线程安全的,而Hashtable和ConcurrenHashMap都是线程安全的。

而Hashtable是很简单粗暴的在它里面的方法上直接加sychornized关键字,并且整个哈希表也只有一个锁。这会引起一系列的性能问题。

CoucurrentHashMap就很聪明的,在哈希表的每个哈希桶(链表)上加上一把锁,并且将扩容这个操作给优化了,它并不是直接一次性扩容到一个新的哈希表中在把旧表里的数据在哈希到新表里,而是在每次插入操作时,分批进行操作。这就避免了突然扩容导致性能需求急剧增加,导致服务器卡死的状况发生。

还有就是HashMap key允许为null

而Hashtable和ConcurrentHashMap的key值是不允许为null的。

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

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

相关文章

yolov7_Obb环境安装

下载obb代码之后&#xff0c;除了安装python和pytorch环境&#xff0c;由于还需要编译nms部分的c代码&#xff0c;因此还需要安装Visual Studio. 这里推荐安装Visual Studio2019版本。 然后在系统环境中配置环境变量 C:\Program Files (x86)\Microsoft Visual Studio\2019\Co…

案例127:基于微信小程序的预约挂号系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

文件按名称分类,批量归类到指定文件夹

我们的生活中充满了各种各样的文件&#xff1a;工作报告、家庭照片、旅行纪念品等&#xff0c;然而文件管理却是一个让人头疼的问题。你是否也曾在寻找某些文件名的重要文件&#xff0c;却因为文件混乱无章的堆放而感到烦躁不安&#xff1f;现在&#xff0c;有了我们【文件批量…

HTML--JavaScript--引入方式

啊哈~~~基础三剑看到第三剑&#xff0c;JavaScript HTML用于控制网页结构 CSS用于控制网页的外观 JavaScript用于控制网页的行为 JavaScript引入方式 引入的三种方式&#xff1a; 外部JavaScript 内部JavaScript 元素事件JavaScript 引入外部JavaScript 一般情况下网页最好…

积极参与建设“一带一路”,川宁生物与微构工场达成战略合作

2024年1月12日&#xff0c;北京微构工场生物技术有限公司&#xff08;以下简称“微构工场”&#xff09;与伊犁川宁生物技术股份有限公司&#xff08;“川宁生物”&#xff09;宣布签订战略合作协议&#xff0c;双方将共同出资设立合资公司&#xff0c;加速生物制造产业化落地&…

Linux操作系统——文件详解

1.文件理解预备知识 首先&#xff0c;当我们在磁盘创建一个空文件时&#xff0c;这个文件会不会占据磁盘空间呢&#xff1f; 答案是当然会占据磁盘空间了&#xff0c;因为文件是空的&#xff0c;仅仅指的是它的内容是空的&#xff0c;但是该文件要有对应的文件名&#xff0c;…

Redis图形界面闪退/错误2系统找不到指定文件/windows无法启动Redis/不是内部或外部命令,也不是可运行的程序

Redis图形界面闪退/错误2系统找不到指定文件/windows无法启动Redis/不是内部或外部命令&#xff0c;也不是可运行的程序 我遇到了以上的问题。 其实&#xff0c;最重要的原因是我打开不了another redis desktop mannager&#xff0c;就是我安装了之后&#xff0c;无法打开它…

【嵌入式学习笔记-02】什么是库文件,静态库的制作和使用,动态库的制作和使用,动态库的动态加载

【嵌入式学习笔记-02】什么是库文件&#xff0c;静态库的制作和使用&#xff0c;动态库的制作和使用&#xff0c;动态库的动态加载 文章目录 什么是库文件&#xff1f;编程模型的发展什么是库文件&#xff1f; 静态库的制作和使用动态库的制作和使用动态库的动态加载 什么是库文…

Docker-01-安装基础命令

Docker-01-安装&基础命令 文章目录 Docker-01-安装&基础命令一、Docker是什么&#xff1f;二、安装Docker①&#xff1a;卸载旧版②&#xff1a;配置Docker的yum库③&#xff1a;安装Docker④&#xff1a;启动和校验⑤&#xff1a;配置镜像加速01&#xff1a;注册阿里云…

SpringBoot知识02

1、快速生成mapper和service &#xff08;1&#xff09;&#xff08;自动生成简单的单表sql&#xff09; &#xff08;2&#xff09;快速生成多表&#xff08;自动生成常量&#xff09; 2、springboot配置swagger&#xff08;路径不用加/api&#xff09; &#xff08;1&#…

Mindspore 公开课 - GPT

GPT Task 在模型 finetune 中&#xff0c;需要根据不同的下游任务来处理输入&#xff0c;主要的下游任务可分为以下四类&#xff1a; 分类&#xff08;Classification&#xff09;&#xff1a;给定一个输入文本&#xff0c;将其分为若干类别中的一类&#xff0c;如情感分类、…

报名活动怎么做_小程序创建线上报名活动最详细攻略

报名活动怎么做&#xff1a;一篇让你掌握活动策划与营销的秘籍 在当今社会&#xff0c;无论是线上还是线下&#xff0c;活动已经成为企业营销和品牌推广的重要手段。但是&#xff0c;如何策划一场成功的活动呢&#xff1f;这篇文章将为你揭示活动策划与营销的秘籍&#xff0c;…

政采网调试要求及常见问题解决方法

登录平台软件环境要求&#xff1a; 操作系统&#xff1a;建议Win10及以上&#xff08;Win10-64位专业版 版本号17134纯净安装版本&#xff09; 浏 览 器&#xff1a;IE11浏览器、谷歌120.0.6099.217&#xff08;64位正式版&#xff09;浏览器 必要软件&#xff1a;CA互联互通…

Mindspore 公开课 - BERT

BERT BERT模型本质上是结合了 ELMo 模型与 GPT 模型的优势。 相比于ELMo&#xff0c;BERT仅需改动最后的输出层&#xff0c;而非模型架构&#xff0c;便可以在下游任务中达到很好的效果&#xff1b;相比于GPT&#xff0c;BERT在处理词元表示时考虑到了双向上下文的信息&#…

微服务架构设计核心理论:掌握微服务设计精髓

文章目录 一、微服务与服务治理1、概述2、Two Pizza原则和微服务团队3、主链路规划4、服务治理和微服务生命周期5、微服务架构的网络层搭建6、微服务架构的部署结构7、面试题 二、配置中心1、为什么要配置中心2、配置中心高可用思考 三、服务监控1、业务埋点的技术选型2、用户行…

2023年总结:雄关漫道真如铁,而今迈步从头越,今朝得失

2023年悄然离去&#xff0c;感谢大家的帮助、鼓励和陪伴&#xff0c;感谢家人的理解和支持&#xff0c;祝大家新年快乐&#xff0c;阖家幸福&#xff0c;身体健康。像往常一样&#xff0c;今年也会写一篇年终总结&#xff0c;也是自己的第11篇年终总结&#xff0c;题目就叫《雄…

32 二叉树的定义

之前的通用树结构 采用双亲孩子表示法模型 孩子兄弟表示法模型 引出二叉树 二叉树的定义&#xff1a; 满二叉树和完全二叉树 对此图要有印象 满二叉树一定是完全二叉树&#xff0c;但是完全二叉树不一定是满二叉树 小结

Javaweb之SpringBootWeb案例员工管理分页查询的详细解析

3. 员工管理 完成了部门管理的功能开发之后&#xff0c;我们进入到下一环节员工管理功能的开发。 基于以上原型&#xff0c;我们可以把员工管理功能分为&#xff1a; 分页查询&#xff08;今天完成&#xff09; 带条件的分页查询&#xff08;今天完成&#xff09; 删除员工&…

HNU-算法设计与分析-实验4

算法设计与分析实验4 计科210X 甘晴void 202108010XXX 目录 文章目录 算法设计与分析<br>实验41 回溯算法求解0-1背包问题问题重述想法代码验证算法分析 2 回溯算法实现题5-4运动员最佳配对问题问题重述想法代码验证算法分析 3 分支限界法求解0-1背包问题问题重述想法…

gogs git创建仓库步骤

目录 引言创建仓库clone 仓库推送代码 引言 Gogs 是一款类似GitHub的开源文件/代码管理系统&#xff08;基于Git&#xff09;&#xff0c;Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自助 Git 服务。 创建仓库 git中的组织可以把它看成是相关仓库的集合&#xff0c…