Java多线程问题

线程

何为线程:线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

线程与进程的关系,区别以及优缺点

一个进程中可以有多个线程,多个线程共享进程的堆和方法区【共享的】(JDK1.8之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈和本地方法栈【私有的】

总结:线程是进程划分成的更小的运行单位,线程和进程的最大不同在于基本上各进程是相互独立的,而各线程则不一定,因为同一进程中的线程极有可能相互影响。线程执行开销小,但不利于资源的管理和保护;而进程则相反。

程序计数器为什么是私有的

程序计数器主要有两个作用:

  1. 字节码解释器通过程序计数器来依次读取指令,从而实现代码的流程控制,如顺序执行、选择、循环、异常处理。
  2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道线程上次运行到哪了。

需要注意的是,如果执行的是native方法,那么程序计数器记录的是undefined地址,只有执行的是Java代码时程序计数器记录的才是下一条指令地址。

所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置

虚拟机栈和本地方法栈为什么是私有的

  • 虚拟机栈:每个Java方法在执行之前会创建一个栈帧用于保存局部变量表、操作数栈、常量池引用等信息,从方法调用直至执行完成的过程,就对应着一个栈帧在Java虚拟机中入栈和出栈的过程。
  • 本地方法栈:和虚拟机栈所发挥的作用相似,区别是:虚拟机栈为虚拟机执行Java方法而服务。本地方法栈为虚拟机使用Native方法服务。

所以,为了保证线程中局部变量不被其他线程访问到,虚拟机栈和本地方法栈都是线程私有的

什么是堆和方法区

堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象(几乎所有对象都在这里分配内存),方法区主要用于存放已经被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

线程的生命周期和状态

New:初始状态,线程被创建出来但没有调用start方法
RUNNABLE:运行状态,线程调用了start()等待运行的状态。
BLOCKED: 阻塞状态,需要等待释放锁。
WAITING:等待状态,表示线程需要等待其他线程做出一些特定动作
TIME_WAITING:等待超时状态,可以在指定的时间后自行返回而不是像WAITING那样一直等待。
TERMINATED:终止状态,表示该线程已经运行完毕。

在这里插入图片描述
线程创建之后处于New状态,调用start()方法后开始运行,线程此时处于Ready状态,可运行状态的线程获得了CPU时间片后就处于Running状态。

在操作系统层面,线程有Ready和Running状态;而在JVM层面,只能看到Runnable状态,所以一般将这两个状态称为Runnable状态。
为什么JVM没有区分这两种状态呢:现在的时分多任务操作系统架构通常都是用所谓“时间分片”方式进行抢占式轮转调度。这个时间分片通常是很小的,一个线程一次最多只能在CPU上运行10-20ms的时间(此时处于Running状态),时间片用完后就要被切换下来放入调度队列的队尾等待再次调度(也即回到Ready状态)。线程切换的如此之快,区分这两种状态就没什么意义了。

  • 当线程执行wait()方法之后,线程进入WAITING状态,进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态。
  • TIMED_WAITING(超时等待):状态相当于在等待状态的基础上增加了超时限制,比如通过sleep(long)或者wait(long)方法可以将线程置于TIMED_WAITING状态。当超时时间结束后,线程将会返回到Runnable状态。
  • 当线程进入synchronized方法/块 或者调用wait后,被notify 重新进入sychronized方法/块,但是锁被其他线程占有,这个时候线程就会进入blocked状态。
  • 线程在执行完了run()方法之后将会进入到TERMINATED(终止)状态。

wait和notify示例代码

package example;

class NotifyExample{
    private static class WaitTask implements Runnable {
        private Object lock;

        public WaitTask(Object lock) {
            this.lock = lock;
        }

        @Override
        public void run() {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + "准备进入等待状态");
                // 此线程在等待lock对象的notify方法唤醒
                try {
                    lock.wait();
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                System.out.println(Thread.currentThread().getName() + "等待结束,本线程继续执行");
            }
        }
    }

    private static class NotifyTask implements Runnable {
        private Object lock;
        public NotifyTask(Object lock) {
            this.lock = lock;
        }
        @Override
        public void run() {
            //!!!重要,获取到lock的线程才能去唤醒在lock上等待的线程
            synchronized (lock) {
                System.out.println("准备唤醒");
                // 唤醒所有线程(随机)
                lock.notifyAll();
                System.out.println("唤醒结束");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        //Object lock2 = new Object();
        Object lock = new Object();
        // 创建三个等待线程
        Thread t1 = new Thread(new WaitTask(lock),"t1");
        Thread t2 = new Thread(new WaitTask(lock),"t2");
        Thread t3 = new Thread(new WaitTask(lock),"t3");
        // 创建一个唤醒线程  Thread notify = new Thread(new NotifyTask(lock2),"notify线程");
        Thread notify = new Thread(new NotifyTask(lock),"notify线程");
        t1.start();
        t2.start();
        t3.start();
        Thread.sleep(100);
        notify.start();
        // 当前正在执行的线程数
        t1.join();
        t2.join();
        t3.join();
        System.out.println(Thread.activeCount() - 1);
    }
}

什么是线程上下文切换

线程在执行过程中会有自己的运行条件和状态(也称上下文),比如上文所提到的程序计数器、栈信息等,当出现如下情况的时候,线程会从占用CPU状态中退出。

  • 主动让出CPU,比如调用了sleep(),wait()等
  • 时间片用完,因为操作系统要防止一个线程或者进程长时间占用CPU导致其他线程或者进程饿死。
  • 调用了阻塞类型的系统中断,比如IO请求,线程被阻塞。
  • 被终止或者运行结束。

这其中前三种都会发生线程切换,线程切换意味着需要保存当前线程的上下文,留待线程下次占用CPU的时候恢复现场。并加载下一个将要占用CPU的线程上下文,这就是所谓的上下文切换。

上下文切换是现代操作系统的基本功能,因其每次需要保存信息恢复信息,这将会占用CPU,内存等系统资源进行处理,也就意味着效率会有一定的损耗,如果频繁切换就会造成整体效率低下。

Thread#sleep()方法和Object#wait()方法对比

共同点:两者都可以暂停线程的执行。
区别:

  • sleep()方法没有释放锁,而wait()方法释放了锁
  • wait()通常被用于线程间交互/通信,sleep通常用于暂停执行。
  • wait方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify或者notifyAll方法。sleep方法执行完成后,线程会自动苏醒,或者也可以使用wait(long)超时后线程会自动苏醒。
  • sleep()是Thread类的静态本地方法,wait()则是Object类的本地方法。(即Thread.sleep()&Object.wait())。

为什么wait()方法不定义在Thread中&为什么sleep方法定义在Thread中

wait是让获得对象锁的线程实现等待, 会自动释放当前线程占有的对象锁。每个对象都拥有对象锁,既然要释放当前线程占有的对象锁并让其进入WAITING状态,自然要操作对应的对象(Object)而费当前的线程(Thread)。

sleep()是让当前线程暂停执行,不涉及到对象类,也不需要获得对象锁。

可以直接调用Thread类的run方法吗?

new一个Thread,线程进入了新建状态。调用start()方法,会启动一个线程并使线程进入就绪状态,当分配到时间片后就可以开始运行了。start()会执行线程的相应准备工作,然后自动执行run()方法,这是真正的多线程工作。但是,直接执行run()方法,会把run方法当作一个main线程下的普通方法去执行,并不会在某个线程中执行他

总结:调用start()方法可启动线程并使线程进入就绪状态,直接执行run()方法就不会以多线程方式执行。

多线程

并发与并行的区别

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

关键点在于并发是时间片的调度,宏观上的并行,并行是真正意义上的同时。

同步与异步的区别

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

单核 CPU 上运行多个线程效率一定会高吗?

在单核CPU上,同一时刻只能有一个线程在运行,其他线程需要等待CPU的时间片分配,如果线程是CPU密集型,那么多个线程同时运行会导致频繁的线程切换,增加了系统开销,降低了效率。如果线程是IO密集型,那么多个线程同时运行可以利用CPU在等待IO时的空闲时间,提高了效率。

因此,对于单核CPU来说,如果任务是CPU密集型,那么多线程反而会影响效率;如果任务是IO密集型,那么多线程会提高效率。

死锁

死锁产生的四个必要条件

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

如何避免和预防死锁

如何预防:破坏死锁产生的必要条件

  • 破坏请求和保持条件:一次性申请所有资源
  • 破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,释放他占用的所有资源。
  • 破坏循环等待条件:按序申请资源,释放资源则反序释放。

如何避免:借助于银行家算法对资源分配进行计算评估,使其进入安全状态。

银行家算法核心内涵:在资源分配过程中,通过模拟分配后系统状态,确保系统始终处于安全状态,从而避免死锁。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OSEK错误处理及跟踪调试

1 前言 如表1所示,OSEK提供了一些特殊的钩子例程(Hook routines),应用层可以在钩子函数中自定义操作,以参与到操作系统的内部处理中。 表1 钩子函数类型 钩子函数例程功能用途ErrorHook用于错误处理StartupHook在系统启…

【论文复现|智能算法改进】基于多策略麻雀搜索算法的机器人路径规划

目录 1.算法原理2.改进点3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】麻雀搜索算法(SSA)原理及实现 2.改进点 改进的无限折叠迭代混沌映射 无限折叠迭代映射(ICMIC) 常用于图像加密方向的研究, 基本思想是首先生成[0,1]之间的混沌序列, …

【Qt秘籍】[008]-Qt中的connect函数

在Qt框架中,connect函数是一个非常核心的函数,用于实现信号(Signals)和槽(Slots)之间的连接,它是Qt信号槽机制的关键所在。信号槽机制是一种高级的通信方式,允许对象在状态改变时通知…

掘金AI 商战宝典-系统班:2024掘金AIGC课程(30节视频课)

课程目录 1-第一讲学会向Al提问:万能提问公式_1.mp4 2-第二讲用AI写视频脚本_1.mp4 3-第三讲用AI写视频口播文案_1.mp4 4-第四讲用AI自动做视频(上)_1.mp4 5-第五讲用AI自动做视频(中)_1.mp4 6-第六讲用AI自动做视…

1. Mybatis基础操作

目录 1.1 需求 1.2 准备 1.3 删除 1.3.1 功能实现 1.3.2 日志输入 1.3.3 预编译SQL 1.3.3.1 介绍 1.3.3.2 SQL注入 1.3.3.3 参数占位符 1.4 新增 1.4.1 基本新增 1.4.2 主键返回 1.5 更新 1.6 查询 1.6.1 根据ID查询 1.6.2 数据封装 1.6.3 条件查询 1.6.4 参…

Tree——输出项目的文件结构(Linux)

输出项目中的文件结构可以使用tree命令。tree是一个用于以树状结构显示目录内容的命令行工具。它非常适合快速查看项目的文件结构。安装: sudo apt-get install tree 使用: 在命令行中导航到项目的根目录,输出文件结构。 tree 也可以将结构输…

NXP RT1060学习总结 - CANFD功能

1、RT1060-CAN FD功能简介 这里使用RT1060系列的1064芯片进行开发,测试板是官方提供的开发板;RT1060系列支持3路CAN功能,CAN1和CAN2只能最为普通的CAN外设,支持CAN2.0,而CAN3支持CAN-FD功能;CAN-FD功能这里…

数据库概念

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. 🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接…

Java学习Lambda表达式

Lambda表达式 有且只有一个未实现的方法叫做Lambda表达式,可以实现函数式编程 // 这个注解是用来检查你写的函数是否是函数式接口 FunctionalInterfaceinterface Myinterface {int sum(int a, int b);default String priteTitle(String name, int age, String sex)…

<Rust><iced>基于rust使用iced库构建GUI实例:动态改变主题色

前言 本专栏是Rust实例应用。 环境配置 平台:windows 软件:vscode 语言:rust 库:iced、iced_aw 概述 本篇构建了这样的一个实例,可以动态修改UI的主题,通过菜单栏来选择预设的自定义主题和官方主题&#…

1+X Web应用软件测试(中级) 考试失利教训

考证考试结束的第二天复盘 复盘时间:2024/6/2 9:45-10:00 考试名称:1X Web应用软件测试(中级) 电子书:Web应用软件测试(中级) 他人备考技巧 改进: [考前策略]自动化40性能40功能2个20 1.可以2人分工合作,完成一整套功能测试题目 2.制定阶段时间的学…

使用PyCharm 开发工具创建工程

一. 简介 前面学习了 安装 python解释器。如何安装python的一种开发工具 PyCharm。 本文来简单学习一下,如何使用 PyCharm 开发工具创建一个简单的 python工程。 二. PyCharm 开发工具创建一个工程 1. 首先,首先打开PyCharm 开发工具。选择 创建一…

社交媒体数据恢复:淘宝旺信

根据搜索结果,阿里旺旺聊天记录的恢复方法如下: 运行阿里旺旺在旺旺面板底部点击小喇叭图标在打开的消息管理器窗口右上角点击"搜索"点击"高级搜索"设置所需查找的时间段---"搜索" 此外,在阿里旺旺的云存储中…

xdp、dpdk、rdma能在同一网卡上同时使用吗?

文心一言 XDP、DPDK和RDMA是三种不同的技术,它们各自在Linux网络性能优化中扮演了不同的角色。关于它们能否在同一网卡上同时使用,我们需要考虑以下几个方面: XDP(eXpress Data Path): XDP是Linux内核提供…

搭建大型分布式服务(三十八)SpringBoot 整合多个kafka数据源-支持protobuf

系列文章目录 文章目录 系列文章目录前言一、本文要点二、开发环境三、原项目四、修改项目五、测试一下五、小结 前言 本插件稳定运行上百个kafka项目&#xff0c;每天处理上亿级的数据的精简小插件&#xff0c;快速上手。 <dependency><groupId>io.github.vipjo…

通过DLL方式链接glfw3.dll

主要是CMakeLists.txt文件变化 cmake_minimum_required(VERSION 3.10) project(glfwTest) set(CMAKE_CXX_STANDARD 11) aux_source_directory(. SRC_SOURCES) add_executable(glfwTest ${SRC_SOURCES}) target_link_libraries(glfwTest opengl32) # 链接库文件 target_inclu…

数据结构(C):从初识堆到堆排序的实现

&#x1f31e;0.前言 言C之言&#xff0c;聊C之识&#xff0c;以C会友&#xff0c;共向远方。各位博友的各位你们好啊&#xff0c;这里是持续分享数据结构知识的小赵同学&#xff0c;今天要分享的数据结构知识是堆&#xff0c;在这一章&#xff0c;小赵将会向大家展开聊聊堆的相…

年薪百万也难达财务自由?揭秘背后的真相!

谈及财务自由&#xff0c;人们往往会好奇&#xff1a;究竟需要多少资金才能跨越这道门槛&#xff1f;根据《胡润财富自由门槛》的调研&#xff0c;中国一线城市的财富自由标准从入门级的人民币1900万元到中级6500万到高级别的1.9亿元不等。然而&#xff0c;财务自由的核心并非仅…

ComfyUi安装OOTDiffusion插件的diffusers版本问题

OOTDiffusion换装 在github上有近5K的star了&#xff08;https://github.com/levihsu/OOTDiffusion&#xff09;。 diffusers版本问题 最新版是0.27.2&#xff0c;不能低于0.25&#xff0c;但是OOT换装需要0.24&#xff0c;否则会报错&#xff1a; ComfyUI\custom_nodes\Comf…

Android精通值Fragment的使用 —— 不含底层逻辑(五)

1. Fragment 使用Fragment的目标&#xff1a;根据列表动态显示内容&#xff0c;更简洁显示界面、查找界面 eg. 使用新闻列表动态显示新闻 1.1 Fragment的特性 具备生命周期 —— 可以动态地移除一些Fragment必须委托在Activity中使用可以在Activity中进行复用 1.2 Fragmen…