首页 > 其他分享 >学习日志:多线程

学习日志:多线程

时间:2024-07-17 20:25:09浏览次数:21  
标签:Thread 学习 死锁 线程 日志 多线程 CPU 资源

文章目录


前言

并发与并行

  • 并发:两个及两个以上的作业在同一 时间段内执行。
  • 并行:两个及两个以上的作业在同一时刻执行。

同步和异步

  • 同步:发出一个调用之后,在没有得到结果之前, 该调用就不可以返回,一直等待。
  • 异步:调用在发出之后,不用等待返回结果,该调用直接返回。

一、多线程的优点

  • 从计算机底层来说: 线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
  • 从当代互联网发展趋势来说: 现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
  • 单核时代:在单核时代多线程主要是为了提高单进程利用 CPU 和 IO 系统的效率。 假设只运行了一个 Java 进程的情况,当我们请求 IO 的时候,如果 Java 进程中只有一个线程,此线程被 IO 阻塞则整个进程被阻塞。CPU 和 IO 设备只有一个在运行,那么可以简单地说系统整体效率只有 50%。当使用多线程的时候,一个线程被 IO 阻塞,其他线程还可以继续使用 CPU。从而提高了 Java 进程利用系统资源的整体效率。
  • 多核时代: 多核时代多线程主要是为了提高进程利用多核 CPU 的能力。举个例子:假如计算一个复杂的任务,我们只用一个线程的话,不论系统有几个 CPU 核心,都只会有一个 CPU 核心被利用到。而创建多个线程,这些线程可以被映射到底层多个 CPU 核心上执行,在任务中的多个线程没有资源竞争的情况下,任务执行的效率会有显著性的提高,约等于(单核时执行时间/CPU 核心数)

二、单核 CPU 支持 Java 多线程

操作系统通过时间片轮转的方式,将 CPU 的时间分配给不同的线程。尽管单核 CPU 一次只能执行一个任务,但通过快速在多个线程之间切换,可以让用户感觉多个任务是同时进行的。

操作系统主要通过两种线程调度方式来管理多线程的执行:

  • 抢占式调度(Preemptive Scheduling):操作系统决定何时暂停当前正在运行的线程,并切换到另一个线程执行。这种切换通常是由系统时钟中断(时间片轮转)或其他高优先级事件(如I/O 操作完成)触发的。这种方式存在上下文切换开销,但公平性和 CPU 资源利用率较好,不易阻塞。
  • 协同式调度(Cooperative Scheduling):线程执行完毕后,主动通知系统切换到另一个线程。这种方式可以减少上下文切换带来的性能开销,但公平性较差,容易阻塞。

Java 使用的线程调度是抢占式的。也就是说,JVM 本身不负责线程的调度,而是将线程的调度委托给操作系统。操作系统通常会基于线程优先级和时间片来调度线程的执行,高优先级的线程通常获得 CPU 时间片的机会更多。

单核 CPU 同时运行多个线程的效率

取决于线程的类型和任务的性质。一般来说,有两种类型的线程:

  1. CPU 密集型:CPU 密集型的线程主要进行计算和逻辑处理,需要占用大量的 CPU 资源。
    (频繁的线程切换,效率较低)

  2. IO 密集型:IO密集型的线程主要进行输入输出操作,如读写文件、网络通信等,需要等待 IO 设备的响应,而不占用太多的 CPU 资源。
    (多个线程同时运行可以利用 CPU 在等待 IO 时的空闲时间,效率较高)


三、线程安全

并发编程的目的就是为了能提高程序的执行效率进而提高程序的运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、死锁、线程不安全等等。

线程安全和不安全是在多线程环境下对于同一份数据的访问是否能够保证其正确性一致性的描述。

  • 线程安全指的是在多线程环境下,对于同一份数据,不管有多少个线程同时访问,都能保证这份数据的正确性和一致性。
  • 线程不安全则表示在多线程环境下,对于同一份数据,多个线程同时访问时可能会导致数据混乱、错误或者丢失。

四、死锁

线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态在这里插入图片描述

public class DeadLockDemo {
    private static Object resource1 = new Object();//资源 1
    private static Object resource2 = new Object();//资源 2

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "线程 1").start();

        new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + "get resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                }
            }
        }, "线程 2").start();
    }
}

产生死锁的四个必要条件:

  1. 互斥条件:该资源任意一个时刻只由一个线程占用。
  2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
  4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

检测死锁

  • 使用jmap、jstack等命令查看 JVM 线程栈和堆内存的情况。如果有死锁,jstack 的输出中通常会有 Found oneJava-level deadlock:的字样,后面会跟着死锁相关的线程信息。另外,实际项目中还可以搭配使用top、df、free等命令查看操作系统的基本情况,出现死锁可能会导致CPU、内存等资源消耗过高。
  • 采用 VisualVM、JConsole 等工具进行排查。

这里以 JConsole 工具为例进行演示。
首先,找到 JDK 的 bin 目录,找到 jconsole 并双击打开。
在这里插入图片描述
打开 jconsole 后,连接对应的程序,然后进入线程界面选择检测死锁即可!
在这里插入图片描述
在这里插入图片描述

预防和避免线程死锁

破坏死锁的产生的必要条件即可:

  1. 破坏请求与保持条件:一次性申请所有的资源。
  2. 破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  3. 破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。
安全状态 指的是系统能够按照某种线程推进顺序(P1、P2、P3……Pn)来为每个线程分配所需资源,直到满足每个线程对资源的最大需求,使每个线程都可顺利完成。称 <P1、P2、P3…Pn> 序列为安全序列。

如上面代码:对线程 2 的代码修改成下面这样就不会产生死锁了。

new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "线程 2").start();

标签:Thread,学习,死锁,线程,日志,多线程,CPU,资源
From: https://blog.csdn.net/weixin_46117680/article/details/140493417

相关文章

  • redis学习-12(实现分布式锁、消息队列、缓存一致性问题、单线程快的原因、跳跃表)
    引用以下内容:redis实现分布式锁:Redis分布式锁-这一篇全了解(Redission实现分布式锁完美方案)Redis实现分布式锁的7种方案,及正确使用姿势!redis实现消息队列Redis的学习教程(十)之使用Redis实现消息队列缓存一致性问题想要保证数据库和Redis缓存一致性,推荐采用先更新数......
  • Splay 学习笔记
    Splay树,或伸展树,是一种平衡二叉查找树,它通过Splay/伸展操作不断将某个节点旋转到根节点,使得整棵树仍然满足二叉查找树的性质,能够在均摊O(\logN)时间内完成插入,查找和删除操作,并且保持平衡而不至于退化为链。Splay树由DanielSleator和RobertTarjan于1985年发明......
  • Task2:从baseline代码详解入门深度学习
    Task2:从baseline代码详解入门深度学习准备工作数据集数据集被划分为三种,分别是:训练集,开发集测试集。训练集数量最多,用于训练模型,开发集用于在训练中不断调整模型的参数,架构,测试集用于测试模型模型基于seq2seq模型主要由encoderdecoder两部分构成使用GRU模型大致可以理......
  • SpringBoot学习笔记
    微服务阶段javaSE:OOPmySQL:持久化html+css+js+jquery+框架:视图,框架不熟练,css不好;javaweb:独立开发MVC三层架构的的网站:原始ssm:框架:简化了我们的开发流程,配置也开始较为复杂;war:tomcat运行spring再简化:springBoot-jar:内嵌tomcat;微服务架构!服务越来越多:springcloud!高内聚,低耦......
  • 7、nginx-日志模块的格式-log_format main、access.log(访问服务器记录的日志)
    日志模块的名称:ngx_http_log_module路径:vim/etc/nginx/nginx.conf相关指令:·日志格式:log_format---nginx有非常灵活的日志模式,每个级别的配置可以有各自独立的访问日志、日志格式通过log_format命令定义··语法Syntax:log_formatname[escape=default|json]......
  • 设计模式之抽象工厂模式(学习笔记)
    定义抽象工厂模式是一种创建型设计模式,它提供一个接口,用于创建一系列相关或依赖的对象,而无需指定它们的具体类。抽象工厂模式将对象的创建过程抽象化,允许子类通过实现具体工厂类来定制对象的创建。为什么使用抽象工厂模式产品族的一致性抽象工厂模式确保同一产品族中的对......
  • 【Powershell】超越限制:获取Azure AD登录日志
    你是否正在寻找一种方法来追踪AzureActiveDirectory(AzureAD)中用户的登录活动?如果是的话,查看AzureAD用户登录日志最简单的方法是使用MicrosoftEntra管理中心。打开https://entra.microsoft.com/,然后进入监视和健康状况->登录日志这里查看到的是全部用户的登录日......
  • 机器学习 -> Machine Learning (III)
    1对抗学习对抗学习的目的是增加鲁棒性。对抗生成网络(GAN)包括生成器(Generator)和判别器(Discriminator)。如果目标是创建能够生成新内容的系统,那么生成器是希望得到并优化的模型,这是一个零和问题。1.1GenBGenB是对抗网络用于VQA的产物,如图添加了偏置模型和目标模型。训练......
  • 机器学习实战笔记2特征编码
    特征编码我们要做的就是将数据的一列目标字段编码importpandasaspddata={'size':['XL','L','M','L','M'],'color':['red','green','blue','green','red']......
  • 机器学习实战笔记3乳腺癌数据集
    乳腺癌数据集1.加载数据集fromsklearn.datasetsimportload_breast_cancerbreast_cancer=load_breast_cancer()print(breast_cancer["DESCR"])输出乳腺癌数据集的详细描述,通常包括数据集的来源、特征的解释、数据集的版权信息等。2.查看data和targetdata=breast_can......