深入解析哈希表、哈希映射和并发哈希映射的区别,以及死锁的成因和解决方案

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 深入解析哈希表、哈希映射和并发哈希映射的区别,以及死锁的成因和解决方案

死锁

死锁是多线程编程中常见的问题,当两个或多个线程互相等待对方持有的资源而无法继续执行时,就会发生死锁。这种情况下,程序会陷入无法恢复的状态,造成程序停滞或崩溃。以下是死锁产生的常见原因和解决方案。

死锁产生条件

  1. 互斥访问资源:多个线程相互竞争访问资源,如果资源被一个线程持有,其他线程无法获取到该资源。
  2. 不可抢占:资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  3. 循环等待:多个线程形成环形的资源依赖关系,每个线程都在等待下一个线程释放资源。
  4. 占用并等待:线程在持有一个资源的同时,又请求其他资源,导致其他线程无法继续执行。
    当上述四个条件都成立时,死锁变形成了。

可以举例“哲学家就餐问题”,有一群哲学家围着一张桌子吃饭,每两个哲学家之间放一个筷子,哲学家只做两件事:思考人生 或者 吃面条。 思考人生的时候就会放下筷子。吃面条就会拿起左右两边的筷子(先拿起左边, 再拿起右边),哲学家发现筷子拿不起来就会阻塞等待思考人生。五个哲学家同一时刻同时拿起左的筷子,再去拿右边的筷子就会发现筷子已被占有,就会阻塞等待,进行思考人生,哲学家们互相挂起等待就会形成“死锁”。

解决方案

如何解决死锁呢,那就是打破死锁形成的四个必要条件。

  • 破坏互斥条件:对于一些非必要的资源,可以改为共享资源,多个线程可以同时访问,从而避免互斥。
  • 破坏占用并等待:当线程需要多个资源时,一次性获取所需资源,而不是一个个依次获取,或者采用资源预分配的策略。
  • 破坏循环等待:通过对资源进行编号或者按照固定的顺序申请资源,避免形成循环等待。

其中最容易破坏的就是 “循环等待”

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

下面分别演示会产生环路等待的代码与不会产生环路等待的代码。

可能会产生环路等待:

public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                //获取锁
                synchronized (lock1){
                    synchronized (lock2){
                    }
                }
            }
        };
        Thread t2 = new Thread(){
            @Override
            public void run() {
                synchronized (lock2){
                    synchronized (lock1){
                    }
                }
            }
        };
        t1.start();
        t2.start();
    }

不会产生环路等待的代码:

public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                //获取锁
                synchronized (lock1){
                    synchronized (lock2){
                    }
                }
            }
        };
        Thread t2 = new Thread(){
            @Override
            public void run() {
                synchronized (lock1){
                    synchronized (lock2){
                    }
                }
            }
        };
        t1.start();
        t2.start();
    }

约定好顺序,先获取lock1锁再获取lock2锁

HashTable

HashTable只是对关键方法加上了 synchronized 关键字。

这就相当于对hashtable对象直接进行加锁。

  • 多个线程访问同一个hashtable就会发送锁冲突
  • 由于同步锁的存在,哈希表的性能相对较差。在高并发情况下,多线程的竞争可能会导致性能下降。
  • 哈希表不允许键和值为 null。

ConcurrentHashMap

  • 每个链表的头结点作为锁对象,降低了锁冲突概率
  • 读操作没有加锁,写操作进行了加锁依旧使用synchronized
  • 充分利用 CAS 特性
  • 优化了扩容方式
  1. 化整为零,当发现需要扩容时,会创建一个新的数组,并进行一部分的数据搬运
  2. 插入新元素时就会直接给新表里插入元素,并搬运一部分数据
  3. 删除元素时,新旧表都会进行查找,元素在那个表上,在那个表上删除
  4. 直到旧表元素搬运完成,才会把旧表进行删除

只有当两个线程访问同一个哈希桶上的数据才会进行锁冲突。

HashMap

  • 线程不安全:哈希映射是非线程安全的,不适用于多线程环境。在多线程环境下使用时,可能导致数据不一致的问题。
  • 性能:哈希映射的性能较好,由于没有同步锁的开销,能够更快地执行插入、查找和删除操作。


相关文章
|
1月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
178 0
|
1月前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
44 3
|
1月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
113 3
|
6天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
14天前
|
监控 关系型数据库 MySQL
MySQL自增ID耗尽应对策略:技术解决方案全解析
在数据库管理中,MySQL的自增ID(AUTO_INCREMENT)属性为表中的每一行提供了一个唯一的标识符。然而,当自增ID达到其最大值时,如何处理这一情况成为了数据库管理员和开发者必须面对的问题。本文将探讨MySQL自增ID耗尽的原因、影响以及有效的应对策略。
51 3
|
18天前
|
存储 人工智能 自然语言处理
高效档案管理案例介绍:文档内容批量结构化解决方案解析
档案文件内容丰富多样,传统人工管理耗时低效。思通数科AI平台通过自动布局分析、段落与标题检测、表格结构识别、嵌套内容还原及元数据生成等功能,实现档案的高精度分块处理和结构化存储,大幅提升管理和检索效率。某历史档案馆通过该平台完成了500万页档案的数字化,信息检索效率提升60%。
|
19天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
16天前
|
存储
文件太大不能拷贝到U盘怎么办?实用解决方案全解析
当我们试图将一个大文件拷贝到U盘时,却突然跳出提示“对于目标文件系统目标文件过大”。这种情况让人感到迷茫,尤其是在急需备份或传输数据的时候。那么,文件太大为什么会无法拷贝到U盘?又该如何解决?本文将详细分析这背后的原因,并提供几个实用的方法,帮助你顺利将文件传输到U盘。
|
23天前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
40 3
|
26天前
|
存储 关系型数据库 MySQL
MySQL MVCC深度解析:掌握并发控制的艺术
【10月更文挑战第23天】 在数据库领域,MVCC(Multi-Version Concurrency Control,多版本并发控制)是一种重要的并发控制机制,它允许多个事务并发执行而不产生冲突。MySQL作为广泛使用的数据库系统,其InnoDB存储引擎就采用了MVCC来处理事务。本文将深入探讨MySQL中的MVCC机制,帮助你在面试中自信应对相关问题。
88 3
下一篇
无影云桌面