(Java高级教程)第二章Java多线程常见面试题-第三节:线程安全集合类和死锁

news2024/11/15 4:16:21

文章目录

  • 一:线程安全集合类
    • (1)多线程环境下使用ArrayList
    • (2)多线程环境使用队列
    • (3)多线程使用哈希表
  • 二:死锁
    • (1)概念
    • (2)死锁产生的四个必要条件
      • A:互斥条件
      • B:不可剥夺条件
      • C:持有并等待条件
      • D:循环等待条件
    • (3)如何处理预防死锁

一:线程安全集合类

在Java集合框架中,大部分都是线程不安全的,当然也存在如Vector这样的线程安全类,但不推荐使用,因为效率太低。所以这里我们对之前学过的集合框架进行说明,以展示他们如何在多线程环境下使用

(1)多线程环境下使用ArrayList

方法一:自己使用同步机制完成,如SynchronizedReentrantLock

方法二:使用collections.synchronizedList(new ArrayList)synchronizedList是标准库提供的一个基于Synchronized进行线程同步的List

  • 此时synchronizedList所有关键操作都会带上synchronized,所以有点粗暴,是一种选择,但不太推荐使用

方法三:使用CopyOnWriteArrayList,它不涉及锁,适用于“一写多读”的场景(也即写的频率较低)

  • CopyOnWrite容器是指“写时拷贝”容器

    • 当我们向一个容器添加元素的时候,不直接添加,而是先把当前容器进行拷贝,拷贝出一个新的容器后,然后向新的容器中添加元素
    • 添加完元素之后,再让原容器的引用指向新的容器
  • 优缺点

    • 优点:可以对原容器进行并发的读,从而不需要加锁;因此在读多写少的情形下,性能很高

    • 缺点

      • 占用内存较多
      • 新写的数据不能在第一时间读到

(2)多线程环境使用队列

主要有以下几种

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

(3)多线程使用哈希表

  • 前面说过,HashMap本身不是线程安全的,而在多线程环境下要想使用哈希表可以有以下两种选择

    • Hashtable:不推荐使用
    • ConcurrentHashMap:下面介绍

ConcurrentHashMap:相较于Hashtable来说,做了很多的优化,更便于使用,主要优化有

①:加锁的粒度变细

  • Hashtable直接对整个对象加锁,一个Hashtable只有一把锁,因此锁竞争非常激烈,只要线程访问Hashtable中的任意数据就会产生竞争
    在这里插入图片描述

  • ConcurrentHashMap只对写操作加锁,并且不是锁整个对象,而是对每个哈希桶分别加锁,这大大降低了锁竞争发生的概率,只有两个线程访问同一个哈希桶时才有锁冲突
    在这里插入图片描述

②:充分利用到了CAS特性

③:对扩容方式进行了优化:扩容过程类似于写时拷贝

  • 扩容过程中,会创建一个新的数组,它会和旧的数组同时存在一段时间
  • 后面每一个来操作ConcurrentHashMap的线程,都会负责将一小部分元素搬运至型数组
  • 在这个过程中,如果要查询元素,那么会在新数组和旧数组上同时进行
  • 在这个过程中,如果要插入元素,那么只在新数组上插入
  • 在搬运完最后一个元素时删除旧数组

二:死锁

(1)概念

死锁:所谓死锁,是指多个进程因竞争资源而造成的一种互相等待的局面,若无外力作用,这些进程将无法向前推进

  • 举例:我拿了你房间的钥匙,而我在自己的房间;你拿了我的房间的钥匙,而你又在自己的房间。如果我要从自己的房间走出去,必须要拿到你手中的钥匙,但是你要走出来又必须要拿到我手中的钥匙,于是形成了死锁

如下是典型的死锁代码

  • thread1首先尝试获取locker1,获取到之后再尝试获取locker2
  • thread2首先尝试获取locker2,获取到之后再尝试获取locker1
  • thread1thread2再尝试获取自己的第二把locker时发生死锁,因为自己想要的locker被对方持有
public class TestDemo6 {
    public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();

        Thread thread1 = new Thread(){
            @Override
            public void run(){
                System.out.println("线程1在尝试获取locker1");
                synchronized (locker1){
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("线程1已获取locker1现在尝试获取locker2");
                    synchronized (locker2){
                        System.out.println("线程1获取locker1和locker2成功");
                    }

                }
            }
        };

        Thread thread2 = new Thread(){
            @Override
            public void run(){
                System.out.println("线程2在尝试获取locker2");
                synchronized (locker2){
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("线程2已获取locker2现在尝试获取locker1");
                    synchronized (locker1){
                        System.out.println("线程2获取locker1和locker2成功");
                    }

                }
            }
        };

        thread1.start();
        thread2.start();
    }
}

在这里插入图片描述

(2)死锁产生的四个必要条件

死锁产生的四个必要条件:死锁必须同时满足以下四个条件才会发生

  • 互斥条件
  • 持有并等待条件
  • 不可剥夺条件
  • 循环等待条件(注意发生死锁一定有循环等待,但是发生循环等待未必死锁)

A:互斥条件

互斥条件:是指只有对必须互斥使用的资源抢夺时才可能导致死锁。比如打印机设备就可能导致互斥,但是像内存、扬声器则不会

在这里插入图片描述

  • 进程A已经获得资源,进程B只能等待

B:不可剥夺条件

不可剥夺条件:是指进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放

在这里插入图片描述

C:持有并等待条件

持有并等待条件:是指进程已经至少保持了一个资源,但又提出了新的资源请求,但是该资源又被其他进程占有,此时请求进程被阻塞,但是对自己持有的资源保持不放

在这里插入图片描述

D:循环等待条件

循环剥夺条件:是指存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求

在这里插入图片描述

(3)如何处理预防死锁

  • 发生死锁有4个必要条件,只要破坏其中之一就可以预防死锁。在这个4个必要条件中互斥、持有并等待和不可剥夺破坏起来都是不太现实的,所以我们一般会在循环等待这个条件上做文章

破坏循环等待条件:可以采用顺序资源分配方法。首先给系统中的资源进行编号,规定每个进程必须按照编号递增的顺序请求资源,编号相同的资源(也就是同类资源)一次申请完

  • 这是因为一个进程只有在已经占有小编号资源的同时,才有资格申请更大编号的资源。所以已经持有大编号资源的进程不可能逆向申请小编号的资源
    在这里插入图片描述

例如对于上面的案例,我们可以让thread1thread2加锁的顺序一致,即都按照先locker1locker2的方式进行加锁,这样的话就不会发生死锁了

public class TestDemo7 {
    public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();

        Thread thread1 = new Thread(){
            @Override
            public void run(){
                System.out.println("线程1在尝试获取locker1");
                synchronized (locker1){
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("线程1已获取locker1现在尝试获取locker2");
                    synchronized (locker2){
                        System.out.println("线程1获取locker1和locker2成功");
                    }

                }
            }
        };

        Thread thread2 = new Thread(){
            @Override
            public void run(){
                System.out.println("线程2在尝试获取locker1");
                synchronized (locker1){
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("线程2已获取locker1现在尝试获取locker2");
                    synchronized (locker2){
                        System.out.println("线程2获取locker1和locker2成功");
                    }

                }
            }
        };

        thread1.start();
        thread2.start();
    }
}

在这里插入图片描述

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

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

相关文章

(小程序)会议OA项目-首页

目录 一、FIex布局简介 1.什么是flex布局? 2.flex属性 学习地址: 3.flex弹性布局演示 ① 容器的属性 ⑴ flex-direction属性 ⑵ flex-wrap属性 ⑶ flex-flow ⑷ justify-content属性 ⑸ align-items属性 ⑹ align-content属性 二、轮播图组件m…

微信记录怎么恢复?恢复已删除微信历史记录的4种方式

恢复已删除微信历史记录的4种方式 如何在有/没有备份的情况下在 iPhone 和 Android 上恢复旧的或已删除的微信历史记录,如聊天对话、语音消息、照片、图片和视频剪辑?参考本指南,祝您成功恢复微信数据。 关于微信数据恢复 “说真的&#xf…

容器化技术Docker与任务编排

Docker容器化 Docker简介 传统的Java项目部署需要自己进行打包,redis,nignx等中间件需要安装以及进行很多配置,稍微繁琐,而Docker使用了容器化的技术把这一过程封装为一条指令解决,而这取决于它的架构设计&#xff0c…

数值优化之函数高阶信息

本文ppt来自深蓝学院《机器人中的数值优化》 目录 1 函数高阶信息的介绍 2 函数高阶信息的计算 1 函数高阶信息的介绍 hessian矩阵是对称矩阵,最后一个公式是函数关于0的泰勒展开 负梯度是函数下降的最快方向 注意区分Hessian矩阵与Jacobian矩阵,Hess…

9_4、Java基本语法之System、Math、BigInteger与BigDecimal类的使用

一、System类的使用 1、System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。 该类位于java.lang包。 由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实 例化该类。其内部的成员变量和成员方法都是static的&am…

【算法题解】7. 反转链表

文章目录题目解法一:迭代解题思路代码实现复杂度分析解法二:递归解题思路代码实现复杂度分析题目 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。来自:leetcode 解法一:迭代 解题思路 使用…

广州车展智能卷王:集度ROBO-01的取与舍

作者 | 德新 编辑 | 王博2022年的最后两天,今年最后一个A级车展在广州开幕。由于种种原因,不少车企匆忙备战或者干脆缺席,这届展会不如往届热闹。但也有憋了大招的选手,比如集度。 12月30日上午,集度在一场非常短的发布…

11-内部类and 12-集合初步

文章目录11-内部类链接外部类使用.this 和.new匿名内部类12-集合初步思考1&#xff0c;List<Apple> apples new ArrayList<>();思考2&#xff0c;如何初始化一个collection思考3&#xff0c;Pet关键字思考4&#xff0c;关于ListIterator这个双向迭代器思考5&#…

保护性暂停设计模式

目录 保护性暂停设计模式 获取结果 产生结果 总代码实现 测试 增加超时效果的Guarded suspension get(long timeout) 测试 保护性暂停设计模式 Guarded Suspension 即 保护性暂停; 是一种等待唤醒机制的一种规范 ,也可以理解为使用中设计模式,Java的API很多都按照保护性…

【免费开放源码】审批类小程序项目实战(活动审批端)

第一节&#xff1a;什么构成了微信小程序、创建一个自己的小程序 第二节&#xff1a;微信开发者工具使用教程 第三节&#xff1a;深入了解并掌握小程序核心组件 第四节&#xff1a;初始化云函数和数据库 第五节&#xff1a;云数据库的增删改查 第六节&#xff1a;项目大纲以及制…

系分 - 面向对象的方法【概念】

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 系分 - &#xff08;概念&#xff09;面向对象的方法 面向对象的方法&#xff08;OO&#xff0c;Object-Oriented&#xff09;是一种基于对象模型的程序设计方法&#xff0c;包括面向对象的分析&#xff08;OOA&a…

【iOS】内存管理

文章目录前言理解引用计数引用计数原理属性存取方法中的内存管理自动释放池保留环以ARC简化引用计数使用ARC时必须遵守的命名规则变量的内存管理语义ARC如何清理实例变量覆写内存管理的方法在dealloc方法中只释放应用并解除监听前言 内存管理&#xff1a; 在Objective-C这样的…

Windows平台下的内存泄漏检测

Windows平台下的内存泄漏检测一、使用_CrtDumpMemoryLeaks定位内存泄露添加对应的头文件转储内存泄漏信息程序任意点退出指定调试信息输出二、定位具体内存泄露位置内存快照转储内存快照比较内存快照完整例子三、使用WinDbg定位获取堆信息查看指定堆的使用情况获取地址信息获取…

【Docker】初级篇

【Docker】初级篇&#xff08;一&#xff09;Docker简介【1】docker是什么【2】容器与虚拟机比较【3】能干嘛【4】去哪下&#xff08;二&#xff09;Docker安装【1】前提说明【2】Docker的基本组成【3】安装步骤&#xff08;1&#xff09;确定是CentOS7及以上版本&#xff08;2…

抽烟打电话行为识别系统 yolo

抽烟打电话行为识别系统通过yolo深度学习框架模型&#xff0c;对现场画面区域进行7*24小时实时监测&#xff0c;发现抽烟打电话等违规行为立即抓拍存档预警。YOLOv5是一种单阶段目标检测算法&#xff0c;该算法在YOLOv4的基础上添加了一些新的改进思路&#xff0c;使其速度与精…

【 shell 编程 】第4篇 数组和函数

数组和函数 文章目录数组和函数一、数组1.普通数组2.关联数组3.数组和循环二、函数1.定义函数2.调用函数一、数组 变量&#xff1a;用一个固定的字符串&#xff0c;代替一个不固定字符串。 数组&#xff1a;用一个固定的字符串&#xff0c;代替多个不固定字符串。 1.普通数组…

Python代码实现学生管理系统

Python代码实现学生管理系统 需求说明 实现一个命令行版本的学生管理系统 功能: 新增学生 显示学生 查找学生 删除学生 存档到文件 创建入口函数 使用一个全局列表 students 表示所有学生信息. 使用 menu 函数和用户交互. 这是一个自定义函数. 使用 insert , show ,…

MacOS Ventura安装失败的原因及解决方法分享

2022年10月&#xff0c;苹果公司向Mac电脑用户推送了MacOS Ventura正式版更新&#xff0c;此次更新为MacOS带来了台前调度、连续互通相机、iMessage 撤回、编辑等功能。吸引众多Mac电脑用户不由纷纷下载安装&#xff0c;但各用户在安装的过程中经常遇到更新MacOS Ventura时突然…

物联网与射频识别技术,课程实验(五)

实验5—— 基于随机二进制树的防冲突算法的实现与性能分析 实验说明&#xff1a; 利用Python或Matlab模拟基于随机二进制树的防冲突算法&#xff1b; 分析标签数量k对遍历所有标签所需时间的影响&#xff1b; 分析标签ID的长度、分布对算法性能的影响&#xff1b; 利用Python或…

MQTT+STM32+ESP8266-01s硬件传递的JSON数据到前端和后端出现中文乱码问题

最近在做一个关于MQTT相关毕设项目,数据传输过程中出现了中文乱码问题,大致就是硬件发送的JSON主题数据中包含中文(如下图1所示),软件后端和软件前端接受该主题数据后出现了中文乱码,出现乱码一般都是硬件传递到后端和前端的编码不一致导致的,所以前端和后端接受该JSON数据的时…