Java并发常见面试题(二)

news2024/11/28 10:56:59

为什么要使用多线程?

从整体上来看

  • 从计算机底层来说: 线程可以看作是轻量级的进程,是最小的程序执行单位,线程间的切换和调度的成本远远小于进程。另外,多核CPU时代,多个线程可以同时运行,这减少了线程上下文切换的开销。
  • 从当代互联网发展趋势来说: 现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。

深入到计算机底层来看

  • 单核时代: 单核时代多线程主要是为了提高单进程利用CPU和IO系统的效率。假设只运行了一个Java进程的情况下,当我们请求IO的时候,如果只有一个线程,此线程被IO阻塞则整个进程被阻塞。CPU和IO设备只有一个在运行,那么可以简单地说系统整体的效率只有50%。如果使用多线程,一个线程被IO阻塞,其他线程还可以继续使用CPU,从而提高了Java进程利用系统资源的整体效率。
  • 多核时代: 多核时代多线程主要是为了提高进程利用多核CPU的能力。假设我们在计算一个复杂的任务,如果只有一个线程,无论有多少个CPU核心,都只会有一个CPU核心被利用到。如果使用多线程,这些线程可以被映射到底层多个CPU核心上执行,任务执行的效率就会显著性的提高,约等于(单核时执行时间/CPU 核心数)。

使用多线程可能带来哪些问题?

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

线程的生命周期和状态

Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:

  • NEW:初始状态,线程被创建出来,但是还没有调用start()方法。
  • RUNABLE:运行中状态,调用了start()方法,Java线程将操作系统中的就绪/可运行(READY)和运行(RUNNING)两种状态统称为RUNABLE(运行中)状态。
  • BLOCKED:阻塞状态,线程阻塞于锁,需要等待锁释放。
  • WATING:等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)。
  • TIMED_WATING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
  • TERMINATED:表示当前线程已经执行完毕。

在这里插入图片描述

  • 由上图可以看出:线程创建之后它将处于 NEW(初始) 状态,调用 start() 方法后开始运行,线程这时候处于 READY(就绪/可运行) 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 RUNNING(运行) 状态。

  • 在操作系统层面,线程有 READY 和 RUNNING 状态;而在 JVM 层面,只能看到 RUNNABLE 状态,所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。

  • 为什么 JVM 没有区分这两种状态呢?
    java 现在的时分(time-sharing)多任务(multi-task)操作系统架构通常都是用所谓的“时间分片(time quantum or time slice)”方式进行抢占式(preemptive)轮转调度(round-robin 式)。这个时间分片通常是很小的,一个线程一次最多只能在 CPU 上运行比如 10-20ms 的时间(此时处于 running 状态),也即大概只有 0.01 秒这一量级,时间片用后就要被切换下来放入调度队列的末尾等待再次调度。(也即回到 ready 状态)。线程切换的如此之快,区分这两种状态就没什么意义了。

  • 当线程执行 wait()方法之后,线程进入 WAITING(等待) 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态。

  • TIMED_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将线程置于 TIMED_WAITING 状态。当超时时间结束后,线程将会返回到 RUNNABLE 状态。

  • 当线程进入 synchronized 方法/块或者调用 wait 后,(被 notify)想要重新进入 synchronized 方法/块时,但是锁被其它线程占有,这个时候线程就会进入 BLOCKED(阻塞) 状态。

  • 线程在执行完了 run()方法之后将会进入到 TERMINATED(终止) 状态。

上下文切换

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

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

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

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

死锁

死锁描述

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

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

死锁产生的四个必要条件

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

如何预防死锁

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

破坏互斥条件 : 这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
破坏请求与保持条件 : 一次性申请所有的资源。
破坏不剥夺条件 : 占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件 : 靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放,破坏循环等待条件。

锁排序法: 指定获取锁的顺序,比如某个线程只有获得A锁和B锁,才能对某资源进行操作,在多线程条件下,如何避免死锁? 通过指定锁的获取顺序,比如规定,只有获得A锁的线程才有资格获取B锁,按顺序获取锁就可以避免死锁。这通常被认为是解决死锁很好的一种方法。

使用显式锁中的ReentrantLock.try(long,TimeUnit)来申请锁。

如何避免死锁?

避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。

安全状态 指的是系统能够按照某种线程推进顺序(P1P2P3.....Pn)来为每个线程分配所需资源,直到满足每个线程对资源的最大需求,
使每个线程都可顺利完成。称 <P1P2P3.....Pn> 序列为安全序列。

sleep() 方法和 wait() 方法对比

共同点:

  • 两者都可以暂停线程的执行。
  • 两者都可以响应中断。

不同点:

  • sleep()方法没有释放锁,而wait()方法释放了锁。
  • sleep()方法通常用于暂停线程的执行,wait()方法通常用于线程间交互/通信。
  • sleep() 方法执行完成后,线程会自动苏醒;wait() 方法被调用后,线程不会自动苏醒,需要其他线程调用同一个对象上的 notify()或者 notifyAll() 方法。或者也可以使用 wait(long timeout) 超时后线程会自动苏醒。
  • sleep()方法是Thread 类的静态本地方法,wait() 则是 Object 类的本地方法。
  • wait()、notify()方法必须写在同步方法/同步代码块中,是为了防止死锁和永久等待,使线程更安全,而sleep()方法没有这个限制。

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

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

为什么 sleep() 方法定义在Thread中?

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

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

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

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

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

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

相关文章

PCB布线及后仿真验证过程(干货满满,建议收藏)

一 布线的基本要求 1. 布线次序考虑 1) 规则驱动布线遵循的基本步骤 定义禁布区&#xff0c;或控制区&#xff1b; 若有规则约束&#xff0c;要求设置规则&#xff1b; 试布线&#xff0c;评估单板是否可以布通&#xff0c;若不能布通&#xff0c;需要采用策略&#x…

Linux测试常用命令

Linux测试常用命令1.Linux安装2.linux常用命令1.一些Linux环境下的基本操作2. 目录管理3.文件管理1.Linux安装 在VMWare虚拟机上安装linux操作系统&#xff0c;得到一个ip&#xff0c;然后通过MobaXterm远程连接linux并进行命令操作。 公司测试的时候是直接拿到服务器ip&…

食品经营许可证办理要什么材料

食品经营许可证办理要什么材料 1.食品经营许可申请书&#xff1b; 2.营业执照或者其他主体资格证明文件复印件&#xff1b; 3.法定代表人&#xff08;负责人&#xff09;和食品安全管理人员的名册、身份证明复印件。申请人委托他人办理食品经营许可申请的&#xff0c;代理人…

代码审计-2 SQL注入

代码审计之SQL注入审计流程ThinkPHP框架ThinkPHP的路由PbootCMSPbootCMS留言处存在SQL注入代码分析ThinkPHP框架 ThinkPHP是一个快速、兼容而且简单的轻量级国产PHP开发框架 MVC模式的php开发框架 MVC&#xff1a;一种软件架构模式&#xff0c;把系统分为三个部分&#xff1a…

基于OpenDaylight和OVSDB搭建VxLAN网络

1 简介 本文主要介绍基于OpenDaylight子项目OVSDB中的southbound组件来搭建VxLAN网络&#xff0c;包括初始环境搭建和southbound RestConf API调用等。OpenDaylight中的northbound组件也可以用来创建VxLAN网络&#xff0c;但northbound不是基于yang的且OVSDB封装的不好&#x…

overflow属性详解

overflow属性详解_桃花扇J的博客-CSDN博客_overflow属性 原链接 overflow是对溢出内容的处理&#xff0c;有四个属性值visible&#xff0c;hidden&#xff0c;scroll&#xff0c;auto&#xff0c;且可以分别设置overflow-x&#xff0c;overflow-y&#xff0c;需要注意的是&am…

从夜间照明到随动转向速锐得解码比亚迪唐车灯升级改装技术方案

汽车大灯犹如人的眼睛一样&#xff0c;在汽车的正脸&#xff0c;灵魂与窗口一样的存在&#xff0c;我们通过技术手段将汽车大灯升级为更高亮度、更智能化&#xff0c;是从根本解决行车安全问题。行车灯发展了几十年&#xff0c;已经不仅限于满足“夜间照明”的功能&#xff0c;…

lt基站学习总结

目录 1.nanocell基站的介 2.lte的网络结构 2.1 网络实体 2.2 功能划分 2.3功能描述 2.4业务类型 2.5语音回落原理 3 S1接口上用户注册消息的简介 3.1 S1接口的位置 3.2 S1接口的协议栈关系 3.3 S1接口建立实例 4 3g用户附着流程 5. Lte用户开机附着过程 1.nanocell基站…

扫描点读笔搭载北京君正X2000多核异构跨界处理器的案例

外研通云畅VT-S30扫描点读笔搭载北京君正X2000多核异构跨界处理器&#xff0c;X2000多核异构跨界处理器主要面向于智能音频、图像识别、智能家电、智能家居、智能办公等五大领域。CPU采取三核结构&#xff0c;搭载双XBurst2&#xff0c;主频1.2GHz&#xff0c;跨界第三核XBurst…

Vue3.0五问五答

1、Vue 3.0 性能提升主要是通过哪几方面体现的&#xff1f; 响应式系统升级 首先来看一下响应式系统升级。我们都知道Vue2的时候&#xff0c;数据响应式的原理使用的是defineProperty&#xff0c;在初始化的时候会遍历data中的所有成员。通过defineProperty&#xff0c;把对象的…

Clion代码提示功能消失无法标出错误代码

Clion就不用多说了吧&#xff0c;jetbrains永远的神&#xff01; 初次遇到这个问题也不知道是怎么回事&#xff0c;突然代码写着写着感到十分顺利。要是按照往常&#xff0c;依据jetbrains的强迫症&#xff0c;非要对你写的代码催毛求疵找出有误的地方&#xff0c;或者警告&…

spark安装与入门

下载 https://archive.apache.org/dist/spark/spark-3.0.0/spark-3.0.0-bin-hadoop3.2.tgz Local 模式 将 spark-3.0.0-bin-hadoop3.2.tgz 文件上传到 Linux&#xff0c;解压 tar -zxvf spark-3.0.0-bin-hadoop3.2.tgz -C /opt/module 重命名 cd /opt/module mv spark-3.0.…

Vue3项目搭建教程

找到一个你想要创建Vue项目的目录&#xff08;本文以C:\Users\XTT\Desktop\新建文件夹\project为例&#xff09;。在此目录打开命令行&#xff0c;输入以下命令&#xff1a; 回车&#xff0c;如果是第一次用这种方式创建Vue3项目&#xff0c;命令行会提示安装 create-vue 包&…

sharedPtr

shared_ptr 1.以何种方式传递 通过下列方式将 shared_ptr 传递给其他函数&#xff1a; 按值传递 shared_ptr。 这将调用复制构造函数&#xff0c;增加引用计数&#xff0c;并使被调用方成为所有者。 此操作的开销很小&#xff0c;但此操作的开销可能很大&#xff0c;具体取决…

python小游戏编程arcade----坦克动画图片合成

python小游戏编程arcade----坦克动画图片合成前言坦克动画图片合成1、PIL image1.1 读取文件并转换1.2 裁切&#xff0c;粘贴1.3 效果图1.4 代码实现2、处理图片的透明度问题2.1 past 函数的三个参数2.2 注意点12.3 注意点22.4 效果![在这里插入图片描述](https://img-blog.csd…

Android中简单使用aspectj

Android中简单使用aspectj 前言&#xff1a; 面向切面编程&#xff08;AOP是Aspect Oriented Program的首字母缩写&#xff09;,这种在运行时&#xff0c;动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程. 1.简介&#xff1a; 在Android中使用注解…

48、线程

一、线程相关概念&#xff1a; 1、程序&#xff08;program&#xff09;&#xff1a; 是为完成特定任务、用某种语言编写的一组指令的集合&#xff0c;即我们写的代码。 2、进程&#xff1a; &#xff08;1&#xff09;进程是指运行中的程序&#xff0c;比如我们使用QQ&…

✿✿✿JavaScript --- BOM、DOM对象

目 录 一、BOM浏览器对象模型 1.Window窗口对象 (1)与弹出有关的方法 (2)与定时器有关的方法 (3)与打开关闭有关的方法 (4) 获取其他对象的属性 2.Location地址栏对象 3.History历史记录对象 二、DOM文档对象模型 1.Document文档对象 (1)获取Element对象 (2)创建…

如何理解CRC循环冗余校验——图解CRC算法模型和C语言实现

如何理解CRC循环冗余校验 循环冗余校验&#xff08;英语&#xff1a;Cyclic redundancy check&#xff0c;通称“CRC”&#xff09;是一种产生定长校验码的算法&#xff0c;主要用来检测或校验数据传输或者保存后可能出现的错误。 它真的太常见了&#xff0c;上至应用软件通信…

Qt QCustomPlot 点状网格线实现和曲线坐标点拾取

Qt QCustomPlot 点状网格线实现和曲线坐标点拾取 文章目录Qt QCustomPlot 点状网格线实现和曲线坐标点拾取摘要我想实现的效果点阵的实现第一版本&#xff0c;使用QPen Style第二版本&#xff0c;通过设置背景第三版本&#xff0c;回到QPen Style取曲线上的点关键字&#xff1a…