当前位置: 首页 > news >正文

实用指南:【JavaEE初阶】多线程重点知识以及常考的面试题-多线程进阶(三)

实用指南:【JavaEE初阶】多线程重点知识以及常考的面试题-多线程进阶(三)

在这里插入图片描述

集合类在多线程下的使用和死锁的知识点还包括常见的面试题.就是本篇博客给大家带来的
文章专栏: JavaEE初阶
若有挑战 评论区见
欢迎大家点赞 评论 收藏 分享
如果你不知道分享给谁,那就分享给薯条.
你们的支持是我不断创作的动力 .

王子,公主请阅

  • 要开心
    • 要快乐
      • 顺便进步
  • 1. 线程安全的集合类
    • 1.1 多线程环境启用ArrayList
    • 1.2 多线程环境使用队列
    • 1.3 多线程环境使用哈希表
    • 1.4 相关面试题
  • 2. 死锁
  • 3. 其他常见面试问题

要开心

要快乐

顺便进步

1. 线程安全的集合类


Vector, Stack, HashTable, 是线程安全的(不建议用), 其他的集合类不是线程安全的.

1.1 多线程环境使用ArrayList

① 运用同步机制 (synchronized 或者 ReentrantLock). ② 利用Collections.synchronizedList(new ArrayList); synchronizedList 是标准库提供的一个基于 synchronized 进行线程同步的 List. synchronizedList的关键操作上都带有synchronized

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

1.2 多线程环境使用队列

① ArrayBlockingQueue基于数组实现的阻塞队列

② LinkedBlockingQueue基于链表实现的阻塞队列
③ PriorityBlockingQueue
基于堆实现的带优先级的阻塞队列
④ TransferQueue
最多只包含⼀个元素的阻塞队列

1.3 多线程环境利用哈希表


线程安全的.就是HashMap 本身不
在多线程环境下可以使用:
① Hashtable
② ConcurrentHashMap

Hashtable:
Hashtable 只是简单的把关键方法加上了 synchronized 关键字
在这里插入图片描述
这相当于直接针对 Hashtable 对象本身加锁.
① 如果多线程访问同一个 Hashtable 就会直接造成锁冲突.
② size 属性也是凭借 synchronized 来控制同步, 也是比较慢的.
③ 一旦触发扩容, 就由该线程完成整个扩容过程. 该过程会涉及到大量的元素拷贝, 效率会非常低.

在这里插入图片描述

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

ConcurrentHashMap 每个哈希桶都有一把锁.只有两个线程访问的恰好示同一个哈希桶上的数据才出现锁冲突.

1.4 相关面试题

1. ConcurrentHashMap的读操作是否要加锁,为什么?
为了进一步降低锁冲突的概率. 为了保证读到刚修改的材料, 搭配了 volatile就是读操作没有加锁. 目的
关键字.

2. 介绍下 ConcurrentHashMap的锁分段技术?
这个是 Java1.7 中采取的技术. Java1.8 中已经不再使用了. 简单的说就是把若干个哈希桶分成一个"段" (Segment), 针对每个段分别加锁.
目的也是为了降低锁竞争的概率. 当两个线程访问的数据恰好在同一个段上的时候, 才触发锁竞争.

3. ConcurrentHashMap在jdk1.8做了哪些优化?
取消了分段锁, 直接给每个哈希桶(每个链表)分配了一个锁(就是以每个链表的头结点对象作为锁对
象).
将原来 数组 + 链表 的实现方式改进成 数组 + 链表 / 红黑树 的方式. 当链表较长的时候(大于等于 8 个元素)就转换成红黑树.

4. Hashtable和HashMap、ConcurrentHashMap 之间的区别?
① HashMap: 线程不安全. key 允许为 null
② Hashtable: 线程安全. 使用 synchronized 锁 Hashtable 对象, 效率较低. key 不允许为 null.
③ ConcurrentHashMap: 线程安全. 使用synchronized 锁每个链表头结点, 锁冲突概率低, 充分利用CAS 机制. 优化了扩容方式. key 不允许为 null


2. 死锁

什么是死锁?
这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止.就是死锁
死锁是⼀种严重的 BUG!! 导致⼀个脚本的线程 “卡死”, 无法正常工作!

关于死锁挑战,很多资料上都谈论到"哲学家就餐障碍".
一张圆桌前坐着五个哲学家,哲学家两边都有一根筷子,桌子中间放有一碗面,要想吃到面条得有两根筷子.

在这里插入图片描述
每个哲学家只做两件事: 思考人生或者吃面条. 思考人生时放下筷子,吃面条时拿起筷子.当筷子被占用时,就只能思考(阻塞等待)
假设同一时刻,五个哲学家同时拿起左手边得筷子,再去拿右手边的筷子,就会发现右手的筷子都被占用了. 哲学家之间互不相让,大家都吃不到面条, 这个时候就形成了死锁.

如何避免死锁?
死锁产生的四个必要条件:
① 互斥使用,即当资源被一个线程采用(占有)时,别的线程不能利用.
② 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
③ 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
④ 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。其中最容易破坏的就是 “循环等待”

破坏循环等待.
最常用的一种死锁阻止技术就是锁排序. 假设有 N 个线程尝试获取 M 把锁, 就可以针对 M 把锁进行编号 (1, 2, 3…M).
N 个线程尝试获取锁的时候, 都按照固定的按编号由小到大顺序来获取锁. 这样就可以避免环路等待.

相关面试题
谈谈死锁是什么?如何避免死锁?实际解决过没有? <
上述内容都是答案./font>

3. 其他常见面试问题

1. 谈谈 volatile关键字的用法?
volatile 能够保证内存可见性. 强制从主内存中读取数据. 此时倘若有其他线程修改被 volatile 修饰的变量, 可以第⼀时间读取到最新的值.

如何构建数据共享的?就是2. Java多线程
JVM 把内存分成了这几个区域:
方法区, 堆区, 栈区, 程序计数器.
其中堆区这个内存区域是多个线程之间共享的.
只要把某个数据放到堆内存中, 就可以让多个线程都能访问到.

3. Java创建线程池的接口是什么?参数 LinkedBlockingQueue 的作用是什么?
创建线程池主导有两种方式:
① 通过 Executors 工厂类创建. 创建方式比较简单, 但定制能力有限.
② 通过 ThreadPoolExecutor 创建. 创建方式比较复杂, 然而定制能力强.

4. Java线程共有几种状态?状态之间怎么切换的?
① NEW: 安排了工作, 还未开始行动. 新创建的线程, 还没有调用 start 方法时处在这个状态.
② RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作. 调用 start 方法之后, 并正在 CPU 上运行/在即将准备运行的状态.
③ BLOCKED: 使用 synchronized 的时候, 如果锁被其他线程占用, 就会阻塞等待, 从而进入该状态.
④ WAITING: 调用 wait 技巧会进入该状态.
⑤ TIMED_WAITING: 调用 sleep 途径或者 wait(超时时间) 会进入该状态.
⑥ TERMINATED: 工作做完了. 当线程 run 方法执行完毕后, 会处于这个状态.

5. 在多线程下,假设对一个数进行叠加,该怎么做?
① 采用synchronized / ReentrantLock 加锁
② 使用 AtomInteger 原子操作.

6. Servlet是否是线程安全的?
① Servlet 本身是工作在多线程环境下.
可能出现线程不安全的情况的.就是② 如果在 Servlet 中创建了某个成员变量, 此时如果有多个请求到达服务器, 服务器就会多线程进行操作,

7. Thread和Runnable的区别和联系?
① Thread 类描述了一个线程.
② Runnable 描述了一个任务.
③ 在创建线程的时候需要指定线程完成的任务, 可以直接重写 Thread 的 run 方式, 也可以采用Runnable 来描述这个任务.

8. 多次start一个线程会怎么样?
第次调用 start 允许成功调用.后续再调用 start 会抛出 java.lang.IllegalThreadStateException 异常

9. 有synchronized两个手段,两个线程分别同时用这个方法,请问会发生什么?
synchronized 加在非静态方法上, 相当于针对当前对象加锁.
这两个方法属于同一个实例:就是若
线程1 能够获取到锁, 并执行方法. 线程2 会阻塞等待, 直到线程1 执行完毕, 释放锁, 线程2 获取到锁之后才能执行方法内容.
如果这两个方法属于不同实例:
两者能并发执行, 互不干扰.

10. 进程和线程的区别?
① 进程是包含线程的. 每个进程至少有⼀个线程存在,即主线程。
② 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.
③ 进程是系统分配资源的最小单位,线程是系统调度的最小单位。

本篇博客到这里就结束啦, 感谢观看 ❤❤❤

期待与你的下一次相遇

http://www.hskmm.com/?act=detail&tid=17755

相关文章:

  • C++ map 和unordered_map 的区别
  • 【英语启蒙动画合集】0基础宝宝必看的动画,超全!直接下载~
  • 基于OPC UA协议的SIMATIC PLC通信实现
  • AI 自动化智能体训练营 | 借助人工智能提升工作效率,打造自己的智能体工作流
  • MX-X21
  • Kubernetes Cilium网络组件和CoreDNS配置
  • 题解:P10107 [GDKOI2023 提高组] 树
  • Gitee Wiki:AI赋能的下一代研发知识管理平台如何重塑软件行业协作范式
  • COLMAP 安装在ubuntu20服务器上问题解决全记录
  • 完整教程:Prompt Tuning提示词微调工程
  • Autodesk Moldflow 2026下载地址与安装教程
  • 程序员利用Python分析股票赚钱,开发了股票行情看板
  • 9.26
  • K8S Deployment 学习
  • 全面掌握 Py2neo 与 Neo4j:从容器化部署到高级应用实战 - 详解
  • 原型
  • 集训队作业1——qoj#11722
  • 如何设置将浏览器网页临时禁用网页mathjax渲染直接查看latex编译前的文本
  • 《IDEA 2025破解 长效使用指南:2099 年有效期配置实战之JetBrains全家桶有效》​
  • Helloworld
  • 基于菲涅尔积分的角锥喇叭方向图计算
  • Flask的ORM工具SQLAlchemy
  • 使用 Rust 和 Tesseract OCR 实现英文数字验证码识别
  • 构建复合AI系统以实现可扩展工作流
  • Python HTTPS 爬虫实战,requests aiohttp Selenium 抓取技巧、HTTPS 问题与抓包调试(python https爬虫、反爬、抓包、证书处理)
  • GreatSQL 优化技巧:最值子查询与窗口函数相互转换
  • Windows Time 时间同步时出错
  • CCS开发环境和TMS320系列DSP实现IP-IQ谐波与无功电流检测
  • 多机动模型PHD滤波算法
  • Navicat17无限试用重置14天