【JUC基础】17. 并发编程常见问题

news2024/11/24 16:19:13

目录

1、前言

2、上下文切换问题

2.1、什么是上下文切换

2.2、上下文切换过程

2.3、上下文切换的原因

2.4、上下文切换的开销和影响

2.5、注意事项和改进策略

3、死锁问题

3.1、什么是死锁

3.2、死锁示例

3.3、改进策略

4、竞态条件

5、内存可见性

6、小结


1、前言

多线程固然可以提升系统的吞吐量,也可以最大化利用系统资源,提升相应速度。但同时也提高了编程的复杂性,也提升了程序调试的门槛。今天就来汇总一些常见的并发编程中的问题。

2、上下文切换问题

2.1、什么是上下文切换

上下文切换是指在多任务环境下,从一个任务(线程或进程)切换到另一个任务时,保存当前任务的状态(上下文)并加载下一个任务的状态的过程。在操作系统中,上下文切换是实现多任务调度的重要机制之一。当系统中存在多个任务需要并发执行时,操作系统通过快速地切换任务的上下文来实现任务的交替执行,以使每个任务都能得到充分的执行时间。

2.2、上下文切换过程

当一个任务被切换出去时,操作系统会保存当前任务的上下文信息,包括寄存器的值、堆栈指针和程序计数器等。然后,操作系统会加载下一个任务的上下文信息,并将控制权转移到该任务中,使其继续执行。这个过程涉及到保存和恢复大量的寄存器状态以及修改内核数据结构,因此,上下文切换是一个相对耗时的操作。

2.3、上下文切换的原因

上下文切换的主要原因包括:

  1. 时间片轮转:操作系统采用时间片轮转调度算法,每个任务被分配一段时间片进行执行,当时间片用完后,任务被切换出去,切换到下一个任务。
  2. 中断处理:当硬件设备发生中断请求时,当前任务会被中断,操作系统需要立即处理中断请求,因此会发生上下文切换。
  3. 等待事件:当任务需要等待某些事件的发生,如等待用户输入、等待IO操作完成等,任务会被阻塞,操作系统会切换到另一个可执行的任务。

2.4、上下文切换的开销和影响

上下文切换虽然是操作系统实现并发的重要机制,但是它也带来了一些开销和影响:

  1. 时间开销:上下文切换需要保存和恢复大量的上下文信息,涉及到寄存器状态的保存和恢复,以及内核数据结构的修改,因此会消耗一定的处理器时间。
  2. 系统资源消耗:上下文切换涉及到内核数据结构的修改和维护,会占用一定的系统资源,包括内存、处理器等。
  3. 性能下降:频繁的上下文切换会导致系统性能下降,特别是在任务数量较多、切换频率较高的情况下。

正因为上下文切换也会有资源的开销,因此多线程开发中并不是线程数量开得越多越好。

2.5、注意事项和改进策略

当涉及到上下文切换时,以下是一些需要注意的事项和改进策略,并通过Java代码示例进行说明:

  • 减少线程数量:

上下文切换的主要开销来自于保存和恢复线程的上下文信息,因此减少线程数量可以减少上下文切换的次数。

ExecutorService executor = Executors.newFixedThreadPool(4); // 使用固定大小的线程池
  • 避免过度线程同步:

过度的线程同步可能导致线程频繁地进入和退出临界区,增加了上下文切换的频率。避免不必要的锁和同步机制。

AtomicInteger counter = new AtomicInteger(0); // 使用原子操作类避免锁竞争
  • 使用非阻塞算法:

非阻塞算法可以减少对共享资源的竞争,避免线程因为等待资源而阻塞,从而减少上下文切换的次数。

ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>(); // 使用非阻塞队列
  • 优化任务调度:

合理的任务调度策略可以减少上下文切换的次数。例如,将相互依赖的任务放在同一个线程中执行,减少线程间的切换。

ForkJoinPool pool = new ForkJoinPool(); // 使用ForkJoinPool进行任务调度
  • 异步编程模型:

使用异步编程模型可以减少线程的阻塞和等待,从而减少上下文切换的发生。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> computeResult()); // 使用CompletableFuture实现异步编程

通过合理的线程池配置、避免过度同步、使用非阻塞算法、优化任务调度和采用异步编程模型,可以降低上下文切换的频率和开销,提高并发程序的性能和效率。但需要注意,在实际开发中,需要根据具体情况选择适当的策略,并进行性能测试和调优以获得最佳的结果。

3、死锁问题

3.1、什么是死锁

死锁是并发编程中常见的问题,指两个或多个线程彼此持有对方所需的资源,并且由于无法继续执行而相互等待的状态。这导致这些线程无法继续执行下去,从而陷入无限等待的状态,进而影响程序的性能和可靠性。

典型的死锁场景通常涉及以下条件的交叉发生:

  1. 互斥条件:至少有一个资源被视为临界资源,一次只能被一个线程占用。
  2. 请求与保持条件:线程在持有至少一个资源的同时,又请求其他资源。
  3. 不可剥夺条件:已分配的资源不能被其他线程强行夺走。
  4. 循环等待条件:存在一组线程,每个线程都在等待下一个线程所持有的资源。

3.2、死锁示例

public class DeadlockExample {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 1 acquired lock1");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("Thread 1 acquired lock2");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread 2 acquired lock2");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("Thread 2 acquired lock1");
                }
            }
        });

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

在上述代码中,两个线程分别尝试获取lock1和lock2的锁,并且获取锁的顺序相反。如果运行该代码,将会导致死锁,因为线程1持有lock1并等待获取lock2,而线程2持有lock2并等待获取lock1,双方相互等待,无法继续执行。

3.3、改进策略

  1. 避免循环等待: 确保线程在获取资源时按照相同的顺序获取,或者使用资源分级,避免循环等待的发生。
  2. 加锁顺序一致性: 线程在获取多个锁时,始终按照相同的顺序获取,避免不同线程之间的锁顺序冲突。
  3. 使用并发库提供的工具:JUC(Java Util Concurrent)包中提供了一些工具类来帮助我们避免死锁的发生,如使用Lock接口及其实现类ReentrantLock代替synchronized关键字进行显式锁定,或者使用java.util.concurrent包中的并发容器来避免手动管理锁。

代码改进:

public class DeadlockExample {
    private static final Lock lock1 = new ReentrantLock();
    private static final Lock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            lock1.lock();
            try {
                System.out.println("Thread 1 acquired lock1");
                Thread.sleep(100);
                lock2.lock();
                try {
                    System.out.println("Thread 1 acquired lock2");
                } finally {
                    lock2.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock1.unlock();
            }
        });

        Thread thread2 = new Thread(() -> {
            lock2.lock();
            try {
                System.out.println("Thread 2 acquired lock2");
                Thread.sleep(100);
                lock1.lock();
                try {
                    System.out.println("Thread 2 acquired lock1");
                } finally {
                    lock1.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock2.unlock();
            }
        });

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

在改进的代码中,我们使用了ReentrantLock来代替synchronized关键字进行显式锁定。这样,我们可以通过调用lock()和unlock()方法来手动管理锁的获取和释放,从而避免死锁的发生。

此外,还有其他一些预防死锁的策略,如:

  1. 资源分配策略: 确保每个线程在请求资源时,能够立即得到所需的资源,而不是无限等待。
  2. 超时策略: 设置一个超时时间,在获取锁或资源的过程中如果超过了该时间仍然无法获取,就放弃并尝试其他方式。
  3. 死锁检测: 可以使用工具来检测死锁的发生,如使用jstack命令查看线程的堆栈信息。

4、竞态条件

竞态条件是指多个线程对共享资源进行操作时,执行的结果依赖于线程执行顺序或时间差的现象。这可能导致不确定的结果或数据一致性问题。

public class RaceConditionExample {
    private int count;

    public void increment() {
        count++;
    }
}

解决方式:使用Synchronized或ReenterLock。

public class RaceConditionExample {
    private int count;

    public synchronized void increment() {
        count++;
    }
}

5、内存可见性

多个线程访问共享变量时,可能会出现内存可见性问题,即一个线程对变量的修改对其他线程不可见。

public class VisibilityExample {
    // 解决方法: 使用`volatile`关键字修饰共享变量,保证其对所有线程的可见性。
    // 或者使用`synchronized`关键字或`Lock`接口来确保线程间的同步和数据可见性。
    private  boolean flag = false;

    public void updateFlag() {
        flag = true;
    }

    public void printFlag() {
        while (!flag) {
            // 等待flag变为true
        }
        System.out.println("Flag is true");
    }
}

6、小结

总之,在并发编程中,需要小心处理常见的问题,包括上下文切换的影响、竞态条件、死锁、内存可见性、阻塞和等待以及资源泄漏等。通过合理的同步机制、线程间通信和资源管理,可以提高程序的性能、稳定性和可维护性。同时,通过合理的代码设计和遵循最佳实践。

 

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

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

相关文章

Hinton:我对“青蛙”创造出“人”这件事的后果很紧张丨全文整理+视频

假如青蛙创造了人&#xff0c;那现在是青蛙控制人类&#xff0c;还是人类控制青蛙&#xff1f;我不知道如何防止这种情况发生。我老了&#xff0c;希望像你们这样年轻而才华横溢的研究人员弄清楚如何拥有这些超级智能&#xff0c;并使我们的生活在不受超级智能控制的情况下变得…

Servlet+jsp+Layui图书管理系统

项目介绍 介绍 使用到了jsp&#xff0c;servlet&#xff0c;Mysql&#xff0c;Java&#xff0c;layui。 大致功能 关于用户&#xff1a; 登录&#xff0c;申请注册&#xff0c;查看搜索图书&#xff0c;查看有关用户的借阅记录&#xff0c;丢失记录&#xff0c;预借记录。对…

详解3DMAX室内建筑效果图的制作渲染过程

如果你并不了解室内、建筑效果图的制作渲染过程,本文将可能对你有一些帮助。 ​什么是 3DMax 渲染? 渲染是利用3dmax软件创建与原始建筑设计或模型精确的 3D 图片的技术。最终效果图在逼真度、精度、细节和真实性方面准确地反映了真实材料和光线。具有经验和专业知识的室内…

Elastic 8.8 版引入了全新的 Learned Sparse Encoder 模型,并宣布正式推出合成监测

作者&#xff1a;Brian Bergholm 2023年5月25日 今天&#xff0c;我们非常高兴地宣布 Elastic 8.8 版正式发布。 新增功能 Elastic 企业搜索可帮助开发人员利用 Elasticsearch 实现强大的现代搜索和发现体验。 请在 “Elastic 企业搜索亮点” 博文或 8.8 版发行说明中&#…

HarmonyOS学习路之开发篇—Java UI框架(TableLayout)

TableLayout TableLayout使用表格的方式划分子组件。 支持的XML属性 TableLayout的共有XML属性继承自&#xff1a;Component TableLayout的自有XML属性见下表&#xff1a; 属性名称 中文描述 取值 取值说明 使用案例 alignment_type 对齐方式 align_edges 表示TableL…

AI实战营第二期 第八节 《MMSegmentation代码课》——笔记9

AI实战营第二期 第八节 《MMSegmentation代码课》 【课程链接】https://www.bilibili.com/video/BV1uh411T73q/ 【讲师介绍】张子豪 OpenMMLab算法工程师 【学习形式】录播社群答疑 【作业布置】本次课程为实战课&#xff0c;需提交笔记作业。 课程大纲&#xff1a; 环境配…

5--Gradle入门 - junit 的使用

5--Gradle入门 - junit 的使用 Gradle 对测试支持 测试任务自动检测并执行测试源集中的所有单元测试。测试执行完成后会生成一个报告。支持JUnit 和 TestNG 测试。 默认测试目录及标准输出 Junit 使用 Gradle 对于Junit4.x 支持 dependencies {testImplementation group: jun…

你们把我当领路人,而你们才是我最大的 “财富“

文章目录 前言一、追忆往昔二、关于编程语言三、算法的重要性四、我是学生&#xff0c;为什么要收我钱&#xff1f;五、视频教程 前言 看到星友的反馈&#xff0c;我觉得做这件事情是我有生以来&#xff0c;做的最正确的一次决策。你们把我当领路人&#xff0c;而实际上你们才是…

YOLOv5/v7 添加注意力机制,30多种模块分析④,CA模块,ECA模块

目录 一、注意力机制介绍1、什么是注意力机制&#xff1f;2、注意力机制的分类3、注意力机制的核心 二、CA模块1、CA模块的原理2、实验结果3、应用示例 三、ECA模块1、ECA模块的原理2、实验结果3、应用示例 大家好&#xff0c;我是哪吒。 &#x1f3c6;本文收录于&#xff0c;…

阿里云企业邮箱设置教程(新手指南)

阿里云企业邮箱怎么使用&#xff1f;企业邮箱快速入门教程&#xff0c;从购买、设置管理员账号密码、创建组织架构账号邮件组、邮箱迁移、切换解析、钉邮绑定与同步&#xff0c;最后启用邮箱&#xff0c;阿里云百科分享阿里云企业邮箱使用教程快速入门&#xff1a; 目录 阿里…

2--Gradle入门 - Groovy简介、基本语法

2--Gradle入门 - Groovy简介、基本语法 Gradle 需要 Groovy 语言的支持&#xff0c;所以本章节主要来介绍 Groovy 的基本语法。 1.Groovy 简介 在某种程度上&#xff0c;Groovy 可以被视为Java 的一种脚本化改良版,Groovy 也是运行在 JVM 上&#xff0c;它可以很好地与 Java 代…

【OpenCV DNN】Flask 视频监控目标检测教程 08

欢迎关注『OpenCV DNN Youcans』系列&#xff0c;持续更新中 【OpenCV DNN】Flask 视频监控目标检测教程 08 3.8 OpenCVFlask实时监控人脸识别控制按钮新建 Flask 项目 cvFlask08cPython程序文件视频流的网页模板程序运行 本系列从零开始&#xff0c;详细讲解使用 Flask 框架构…

Nature子刊:生物合成硝基化蛋白,助力解决药物免疫耐受!

氨基酸是蛋白质的单个构建模块&#xff0c;对生物系统的正常运转至关重要。所有生物系统中的蛋白质都是由20种标准氨基酸组成的&#xff0c;自然界中还发现了超过500种不同类型的其他氨基酸&#xff0c;以及大量的人造氨基酸。其中一些替代氨基酸有助于创造新类型的药物和治疗方…

阿里企业邮箱注册流程(新手指南)

阿里云企业邮箱购买流程&#xff0c;企业邮箱分为免费版、标准版、集团版和尊享版&#xff0c;阿里云百科分享企业邮箱版本区别&#xff0c;企业邮箱收费标准价格表&#xff0c;以及阿里企业邮箱详细购买流程&#xff1a; 目录 阿里云企业邮箱购买流程 一、阿里云账号注册及…

驱动开发:内核ShellCode线程注入

还记得《驱动开发&#xff1a;内核LoadLibrary实现DLL注入》中所使用的注入技术吗&#xff0c;我们通过RtlCreateUserThread函数调用实现了注入DLL到应用层并执行&#xff0c;本章将继续探索一个简单的问题&#xff0c;如何注入ShellCode代码实现反弹Shell&#xff0c;这里需要…

ChatGPT 背后的技术重点:RLHF、IFT、CoT、红蓝对抗

近段时间&#xff0c;ChatGPT 横空出世并获得巨大成功&#xff0c;使得 RLHF、SFT、IFT、CoT 等这些晦涩的缩写开始出现在普罗大众的讨论中。这些晦涩的首字母缩略词究竟是什么意思&#xff1f;为什么它们如此重要&#xff1f;我们调查了相关的所有重要论文&#xff0c;以对这些…

Go1.21 速览:go.mod 的 Go 版本号将会约束 Go 程序构建,要特别注意了!

大家好&#xff0c;我是煎鱼。 之前 Go 核心团队的负责人 Russ Cox 针对 Go 的向前兼容&#xff08;指的是旧版本的 Go 编译新的 Go 代码&#xff09;&#xff0c;进行了进一步的设计。 重点内容如下&#xff1a; 新增 GOTOOLCHAIN 环境变量的设置。改变在工作模块&#xff08;…

阿里云弹性公网EIP收费价格表

阿里云弹性公网EIP怎么收费&#xff1f;EIP地域不同价格不同&#xff0c;EIP计费模式分为包年包月和按量付费&#xff0c;弹性公网IP可以按带宽收费也可以按使用流量收费&#xff0c;阿里云百科分享阿里云弹性公网IP不同地域、不同计费模式、按带宽和按使用流量详细收费价格表&…

cpp新小点1

这里写目录标题 argc argv继承虚继承多态override不加override overload纯虚函数和抽象类虚析构和纯虚析构 static和 constexternself前置 后置默认构造 析构继承构造函数不能是虚函数派⽣类的override虚函数定义必须和⽗类完全⼀致。 有特列何时共享虚函数地址表 智能指针arrm…

【数据库必备知识】上手表设计

目录 &#x1f4d6;前言 1. 基本步骤 1.1 梳理清楚需求中的实体 1.2 梳理清楚实体间的关系 2. 实体间的三种关系 2.1 一对一 2.2 一对多 2.3 多对多 &#x1f389;小结ending &#x1f4d6;前言 本文讲解的是基本的表设计, 设计一般只有在有一定实际项目经验后, 才能…