多线程(十):总结

2023-11-17

本章用来处理一下之前遗漏的很多问题,在多线程那一章,很多常见面试题都没有讲,这里再来补充一下。

HashTable, HashMap, ConcurrentHashMap 之间的区别

HashTable, HashMap, ConcurrentHashMap 都带有Map,它们其实都是 Map 的接口,都是以键值对的 形式来存储数据。

HashMap

HashMap是在JDK1.2中引入的Map的实现类。

HashMap是基于哈希表实现的,其主要的特点有:

  1. HashMap 的键值对均可以为 null(当key 为null 时,哈希会被赋值为0)
  2. 初始的size 默认为 16,每次以二倍的形式扩容,最大值为 2^30 
  3. 底层使用的数据结构为:数组 + 链表 + 红黑树
  4. 当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀;计算index方法:index = hash & (tab.length – 1)
  5.  HashMap 效率非常高,但线程不安全

HashTable

Hash table,叫做散列表(也叫哈希表),其主要特点有:

  1. 底层是由 数组 + 链表 实现的
  2. 无论是 key 还是 value 都是 不允许为 null 的
  3. 虽然线程是安全的,但是只是简单得用 synchronized 给所有方法加锁,相当于是对this加锁,也就是对整个HashTable对象进行加锁(非常无脑)                                      因为是 无脑加锁,所以Java官方并不推荐使用,而建议不涉及到线程安全问题时使用:HashMap,遇到线程安全问题时 使用:ConcurrentHashMap
  4. 实现线程安全的方式是在修改数据时锁住整个HashTable,所以效率非常低
  5. 初始size为11,扩容:newsize = olesize*2+1
    计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length【我查的】

ConcurrentHashMap

  1. 底层数据结构:数组 + 链表 + 红黑树
  2. ConcurrentHashMap 的键值不可以为null
  3. ConcurrentHashMap 最重要的点要说 线程安全

    ConcurrentHashMap 相比比较于HashTable 有很多的优化

核心思想就是降低 锁冲突的概率:

具体的优化手段有:

(1)锁粒度的控制

ConcurrentHashMap 不是锁整个对象,而是使用多把锁,对每个哈希桶(链表)都进行加锁,只有当两个线程同时访问同一个哈希桶时,才会产生锁冲突,这样也就降低了锁冲突的概率,性能也就提高了

(2) 只给读加锁,不给写加锁

我们知道 写会造成冲突,而只读不会有影响。

(3)充分利用到了CAS的特性

比如更新元素个数,都是通过CAS来实现的,而不是加锁

(4)ConcurrentHashMap 对于扩容操作,进行了特殊优化

HashTable的扩容是这样:当put元素的时候,发现当前的负载因子已经超过阀值了,就触发扩容。

扩容操作时这样:申请一个更大的数组,然后把这之前旧的数据给搬运到新的数组上

但这样的操作会存在这样的问题:如果元素个数特别多,那么搬运的操作就会开销很大

执行一个put操作,正常一个put会瞬间完成O(1)

但是触发扩容的这一下put,可能就会卡很久(正常情况下服务器都没问题,但也有极小概率会发生请求超时(put卡了,导致请求超时),虽然是极小概率,但是在大量数据下,就不是小问题了)

ConcurrentHashMap 在扩容时,就不再是直接一次性完成搬运了

而是搬运一点,具体是这样的扩容过程中,旧的和新的会同时存在一段时间,每次进行哈希表的操作,都会把旧的内存上的元素搬运一部分到新的空间上,直到最终搬运完成,就释放旧的空间在这个过程中如果要查询元素,旧的和新的一起查询;如果要插入元素,直接在新的上插入;如果是要删除元素,那就直接删就可以了

具体的可以参考下面两篇博客:

 ConcurrentHashMap_亦安✘的博客-CSDN博客

死锁的成因, 和解决方案

什么是死锁

所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。

产生死锁的三个经典案例

案例一:一个线程 一把锁

一个线程正常操作,不会发生线程安全问题,如果是说,针对一个线程,多次加锁,那么就会产生问题。

// 第一次加锁成功

lock();

// 再次尝试对其加锁,原来的锁还未被释放

lock();

// 加锁失败,造成阻塞等待

像这样,第二次加锁,再等待第一次加锁的资源释放,第一次加锁释放又在等待第二次加锁的完成,于是只能造成死锁。

对可重入锁和不可重入锁的补充

 如果同一个线程在重复获取同一把锁的过程中,形成了死锁。这把锁又被称为不可重入锁。而可重入锁的字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁,不会出现死锁的情况。synchronized 是可重入锁

案例二:(两个线程,两把锁)

简单理解就是 车钥匙 在 家里 , 家钥匙 在 车里。

简单用伪代码来距离:

Object locker1 = new Object();
Object locker2 = new Object();


// 线程 T1
synchronized (locker1) {
    synchronized (locker2) {

    }
}


// 线程 T2
synchronized (locker2) {
    synchronized (locker1) {

    }
}

线程抢占式执行,假设 T1 和 T2 同时执行,T1拿到了 locker1 ,T2 拿到了locker2 ,都卡在了第一步,要想拿到 另一把锁,必须得让对方先释放,双方都无法释放,那么就造成了死锁。

案例三:(N个线程,M把锁)

哲学家吃面条问题

5位哲学家围着一张桌子,桌子上有几碗面条。这5位哲学家的左右手两边各有一根筷子(注意是一根,不是一双,两根筷子才是一双,才能拿来吃面,一根筷子无法吃面)

 5位哲学家相当于是5个线程,这些线程只有分别拿到左右手旁的两根筷子(各自要求的两把锁),才能完成进程,并释放自己所占用的锁。 

然后呢,在某一时刻,哲学家都想吃面条:他们同时拿起了自己右手边的那根筷子。5位哲学家、5根筷子,他们每个人都只拿了一根筷子(获取到了一个锁) 。于是他们每个人都完成不了各自的进程,也无法释放他们所占用的锁(筷子),都吃不到面条。

这又是一个死锁问题。

 解决办法

那么怎么解决呢?和上面死锁的解决方案相同——我们要分析为什么会出现死锁,就是因为线程对锁的互相等待,线程一要获取的锁被线程二占用着,但同时线程二要获取的锁又被线程一占用着,于是他们两个都无法获取到完整的锁,无法完成各自的进程,并释放锁。都处于一个循环等待的过程。

要解决死锁问题,重点就是解决循环等待问题。如果每个线程都按一定的顺序来获取对应的锁,比如在上面的栗子中,我们给5根筷子(5把锁)按从1到5的顺序进行编号,哲学家只能拿到到左右两边锁编号最小的那把锁。(已经拿到的锁不用进行编号的比较)

 形成死锁的四个条件

  1. 互斥性:当多个线程对同一把锁,有竞争。在某一时刻,最终只有一个线程可以拥有这把锁
  2. 不可抢夺性:当一个线程已经获取到了锁A,其他线程要想获取锁A,这个时候只能等该线程把A释放了之后再获取,不能中途抢夺别的线程的锁。
  3. 请求和保持性:当一个线程获取到了锁A,除非该线程自己释放锁A,否则该线程就一直保持占有锁A
  4. 循环等待性:在死锁中往往会出现,线程A等着线程B释放锁,同时线程B又在等着线程A来释放他所占有的锁,结果A、B的锁都无法正常释放,也都无法完成各自的进程,陷入了一个循环等待的状态

当上述四个条件某一条被破坏之后,死锁就解决了。

synchronized

synchronized的特点:

  1. 既是乐观锁,又是悲观锁
  2. 既是轻量级锁,又是重量级锁
  3. 轻量级锁基于自旋锁,重量级锁基于挂起等待锁
  4. 不是读写锁
  5. 是可重入锁
  6. 是非公平锁

synchronized关键字通常使用在下面四个地方:

  • synchronized修饰实例方法。

  • synchronized修饰静态方法。

  • synchronized修饰实例方法的代码块。

  • synchronized修饰静态方法的代码块。

锁升级

Java 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

如下图:

在这里插入图片描述

锁策略, cas 和 synchronized 优化过程

可以参考之前写过的文章:

 多线程(八):常见锁策略_我可是ikun啊的博客-CSDN博客

synchronized 和 ReentrantLock 之间的区别

相同点:

  1. synchronized 和 ReentrantLock 都是 Java 中提供的可重入锁

不同点:

  1. 用法不同:synchronized 可以用来修饰普通方法、静态方法和代码块;ReentrantLock 只能用于代码块;
  2. 获取和释放锁的机制不同:进入synchronized 块自动加锁和执行完后自动释放锁; ReentrantLock 需要显示的手动加锁和释放锁;
  3. 锁类型不同:synchronized 是非公平锁; ReentrantLock 默认为非公平锁,也可以手动指定为公平锁;
  4. 响应中断不同:synchronized 不能响应中断;ReentrantLock 可以响应中断,可用于解决死锁的问题;
  5. 底层实现不同:synchronized 是 JVM 层面通过监视器实现的;ReentrantLock 是基于 AQS 实现的。

线程池的执行流程和拒绝策略

线程池的执行流程:

  1. 当新加入一个任务时,先判断当前线程数是否大于核心线程数,如果结果为 false,则新建线程并执行任务;
  2. 如果结果为 true,则判断任务队列是否已满,如果结果为 false,则把任务添加到任务队列中等待线程执行
  3. 如果结果为 true,则判断当前线程数量是否超过最大线程数?如果结果为 false,则新建线程执行此任务
  4. 如果结果为 true,执行拒绝策略。

拒绝策略:

  1. AbortPolicy:中止策略,线程池会抛出异常并中止执行此任务;
  2. CallerRunsPolicy:把任务交给添加此任务的线程来执行;
  3. DiscardPolicy:忽略此任务(最新加入的任务);
  4. DiscardOldestPolicy:忽略最先加入队列的任务(最老的任务)。

线程池的执行流程图:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多线程(十):总结 的相关文章

随机推荐

  • 区块链-公钥生成地址

    目录 https blog csdn net qq 40452317 article details 89646633 比特币 区块链 地址是一个由数字和字母组成的字符串 由公钥 一个同样由数字和字母组成的字符串 生成的比特币地址以数字 1
  • UE4/UE5 动画控制

    工程下载 https mbd pub o bread ZJ2cm5pu 蓝图控制sequence播放 倒播动画 设置开启鼠标指针 开启鼠标事件 在场景中进行过场动画制作 设置控制事件
  • 滚动页面触发相应位置动画 ---react

    需要实现的效果 滚动到内容区域触发 第一段内容移动效果 第二段内容淡入 第三段内容缩放 实现思路 滚动过的距离 当前窗口的高度 gt 元素到顶部窗口的距离 gt 则触发动画 整体代码 import React useRef useEffec
  • Rust 取代 C++ ?

    https www zhihu com question 27608498 作者 天象 链接 https www zhihu com question 27608498 answer 50130876 来源 知乎 著作权归作者所有 商业转载
  • CSS 文字特效运用目录

    主要是记录文字相关的特效实践案例和实现思路 章节名称 完成度 难度 文章地址 完整代码下载地址 创意填充文本悬停效果 完成 一般 文章链接 代码下载 发光文字跟随鼠标 完成 一般 文章链接 代码下载 酷炫的文字悬停效果 完成 一般 文章链接
  • checkbox中checked属性总结

    一 checked属性定义和用法 1 checked属性是一个布尔属性 2 checked属性规定在页面加载时应该被预先选定的
  • 数据库连接的5种方式

    数据库连接的5种方式 String url jdbc mysql localhost 3306 njustzjc1 Properties properties new Properties properties setProperty us
  • 基础理解之SESSION

    SESSION是服务器端的一种会话机制 当客户端的请求服务器创建一个SESSION时 服务器会先检测该请求里面是否包含一个惟一的sesionid 如果是 说明服务器已经为该用户创建过SESSION 只要按照该sesionid检索出该用户的s
  • 在钉钉上怎么手写_大庆市第十中学

    有关直播的那些事儿 2020 停课不停学 十中在行动 根据大庆市教育局关于网络教学工作的要求和指导意见 我校将于3月2日全面开展网络教学工作 虽然网络直播教学与线下日常教学主旨都是一样的 但由于形式的差异 对于很少体验过网络直播教学的老师来
  • 红米ac2100 刷openwrt以及刷回记录

    redmiac2100 刷机 参考 手动升级漏洞固件 https wwx lanzoux com i6iqxhqp98f 或者百度网盘链接 https pan baidu com s 1H355Ym9p TLrVOux2w2b7Q 提取码
  • Flutter开发之——Icon图标

    一 概述 Icon是支持material design的一系列图标 Icon类似于iconfont即字体图标 它是将图标做成字体文件 然后通过指定不同的字符显示不同图片 二 Icon说明 2 1 说明 在字体文件中 每一个字符都对应一个位码
  • Android内存管理

    Android内存泄露 全解析和处理办法 http www jianshu com p bf159a9c391a
  • 产品运行所需的信息检索失败。请重新安装xshell

    产品运行所需的信息检索失败 请重新安装xshell 很久没有应用Xshell进行远程服务器连接了 由于需要应用远程云计算资源 因此有需要使用这个软件 但是在今天的使用过程中出现了 问题 打开Xshell之后 找到可执行文件之后 点击运行 管
  • 【CQOI 2015】任务查询系统

    题目 传送门 题目描述 最近实验室正在为其管理的超级计算机编制一套任务管理系统 而你被安排完成其中的查询部分 超级计算机中的任务用三元组 Si Ei Pi 描述 Si Ei Pi 表示任务从第 Si 秒开始 在第 Ei 秒后结束 第 Si
  • 电脑开不了机启动不了

    故障现象 解决办法 1 先不要操作别的 首先想想开机之前做过什么操作 更新过什么 补丁 漏洞 软件 还是别的等等 2 重启操作系统 在开机的时候不停地按F8键 如果不停地按之后出现了一个黑底白字的菜单 可以最后一次配置试试 不行可以进入系统
  • 服务器CPU经常跑高是什么原因

    服务器在使用过程中 经常会遇见这样的情况 在长时间使用之后 系统运行会越来越慢 卡的情况 查询后台进程 CPU占用以超过90 那么高的CPU使用率 都是会由哪些因素导致的呢 1 散热故障 如机房散热不足 温度过热或者驱动故障 导致温度太高
  • html新闻滚动效果,js实现滚动新闻效果

    code js cn a display block font size 15px line height 18px text decoration none color 333 font family Arial font size 12
  • 机器学习-泛化能力笔记

    1 什么是泛化能力 在机器学习方法中 泛化能力通俗来讲就是指学习到的模型对未知数据的预测能力 在实际情况中 我们通常通过测试误差来评价学习方法的泛化能力 2 泛化误差的定义 大家马上应该发现 这个不是损失函数的期望吗 没错 泛化误差就是所学
  • C++多线程(七):unique_lock详解

    目录 unique lock取代lock guard unique lock的第二个参数 std adopt lock std try to lock std defer lock unique lock的成员函数 成员函数lock 成员函
  • 多线程(十):总结

    本章用来处理一下之前遗漏的很多问题 在多线程那一章 很多常见面试题都没有讲 这里再来补充一下 HashTable HashMap ConcurrentHashMap 之间的区别 HashTable HashMap ConcurrentHas