面试篇:多线程

news2024/11/16 23:41:49

一、线程和进程的区别?

1、进程

程序由指令数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理IO的。

当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。

多实例进程就是可以打开多个

单实例进程就是只能打开一个 

2、线程 

一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行

一个进程之内可以分为一到多个线程。

 3、进程和线程对比

  • 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务
  • 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)

二、并行和并发有什么区别?

1、单核CPU

  • 单核CPU下线程实际还是串行执行的。
  • 操作系统中有一个组件叫做任务调度器,将pu的时间片(windows下时间片最小约为15 毫秒)分给不同的程房使用,只是由于cpu在线程间(时间片很短)的切换非常快,人类感觉是同时运行的。
  • 总结为一句话就是: 微观串行,宏观并行。
  • 一般会将这种线程轮流使用CPU的做法称为并发 (concurrent)。

 对于单核CPU而言,一次只能执行一个线程,不过每个线程执行特别快,并且存在CPU调度,所以每个CPU执行一个线程,但是多个线程轮回切换。 

2、多核CPU

每个核(core)都可以调度运行线程,这时候线程可以是并行的。 

第一个时间片

 第二个时间片

 第三个时间片

第四个时间片

 3、并发和并行的区别

  • 如果是在单核CPU上,多个任务交替执行,那么就是并发。
  • 如果是在多核CPU上同时执行多个任务,那么就是并行。

三、线程创建的方式

1、继承Thread类

  • 继承Thread类
  • 重写run方法
  • 创建继承Thread类的实例
  • 调用start()方法启动线程

2、实现Runnable接口

  • 实现Runnable接口
  • 重写run方法
  • 创建实现Runnable接口的对象的实例
  • 创建Thread类对象,并将实例包装在Thread类中
  • 运行start方法启动线程

3、实现Callable接口

  • 实现Callable接口
  • 重写call方法
  • 创建实现Callable的类的实例
  • 创建Futuretask方法,将实例包装进去
  • 创建Thread类,将Futuretask实例包装进去
  • 执行start方法,开启线程
  • 如果需要获得返回值,调用futuretask实例的get()方法获取返回值

4、线程池创建线程

  • 实现Runnable接口
  • 重写run()方法
  • 创建线程池对象
  • 提交任务
  • 关闭线程池

5、Runnable 和 Callable 有什么区别?

  • Runnable 接口run方法没有返回值。
  • Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
  • Callable接口的()方法允许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛。

6、在启动线程的时候,可以使用run方法吗? run()和 start()有什么区别?

  • 线程启动时候,只能使用start方法开启线程
  • run方法执行只是使用当前主线程去执行这个方法
  • start方法是创建一个线程,然后通过子线程去执行这个方法
  • 同一个run方法可以执行多次,同一个start方法执行执行一次

四、线程包括哪些状态,状态之间是如何变化的?

1、线程的状态

  • 新建
  • 就绪
  • 运行
  • 阻塞
  • 死亡

  •  当创建一个线程但未执行start()方法,此时是新建状态
  • 当执行start()方法就是就绪状态,等待CPU的调度
  • 当获取CPU的调度的时候就是运行状态
  • 当运行中线程无法获取锁、执行了wait()方法、执行sleep()方法,就进入阻塞状态
  • 当阻塞中的线程获取锁,执行了notify()方法,等待时间过期,就进入就绪状态
  • 当线程执行完毕,就进入死亡状态

2、概括

线程包括哪些状态

  • 新建(NEW)
  • 可运行(RUNNABLE)
  • 阻塞(BLOCKED)
  • 等待 (WAITING)
  • 时间等待(TIMEDWALTING)
  • 终止(TERMINATED) 

线程状态之间是如何变化的

  • 创建线程对象是新建状态
  • 调用了start()方法转变为就绪状态
  • 线程获取到了CPU的执行权,执行结束是终止状态在就绪状态的过程中,如果没有获取CPU的执行权,可能会切换其他状态 
    • 如果没有获取锁(synchronized或lock) 进入阻塞状态,获得锁再切换为可执行状态。
    • 如果线程调用了wait()方法进入等待状态,其他线程调用notify()唤醒后可切换为可执行状态。
    • 如果线程调用了sleep(50)方法,进入计时等待状态,到时间后可切换为可执行状态。

五、新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?

使用join()方法解决

代码例子:

  • 首先创建线程t1
  • 然后创建线程t2,在线程t2中加入t1.join()方法,就会阻塞当前线程让t1线程先执行完,然后再执行当前线程。
  • 然后在线程t3加入t2.join()方法,就会阻塞当前线程让t2线程先执行完,再执行当前线程。

六、notify()和 notifyAlI()有什么区别?

  • notifyAll:唤醒所有wait的线程
  • notify: 只随机唤醒一个 wait 线程

七、java中wait和sleep方法的区别?

1、共同点

 wait0,wait(long)和 sleep(long)的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态

2、不同点

方法归属不同

  • sleep(long)是Thread 的静态方法。
  • 而wait(),wait(long)都是Object 的成员方法,每个对象都有。

醒来时机不同

  • 执行 sleep(long)和 wait(long)的线程都会在等待相应毫秒后醒来。
  • wait(long)和 wait0 还可以被 notify 唤醒,wait0 如果不唤醒就一直等下去。
  • 它们都可以被打断唤醒

锁特性不同(重点)

  • wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制。
  • wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)。
  • 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁 (我放弃 cpu,你们也用不了)。

八、如何停止一个正在运行的线程?

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  • 使用stop方法强行终止(不推荐,方法已作废)。
  • 使用interrupt方法中断线程:
    • 打断阻塞的线程 ( sleep,wait,join )的线程,线程会抛出InterruptedException异常。
    • 打断正常的线程,可以根据打断状态来标记是否退出线程。

九、Sychronized底层原理

1、Sychronized基本使用

如上图,对于抢票,如果不加锁,就会出现超卖或者同一张票多次出售的情况。为了避免这种情况就需要使用Sychronized。如下图结果:

Synchronized(对象锁)采用互斥的方式让同一时刻至多只有一个线程能持有(对象锁),其它线程再想获取这个(对象锁)时就会阻塞住。

2、synchronized关键字的底层原理-基础

  • Monitor:翻译为监视器,由JVM提供,C++语言实现
  • Owner:存储当前获取锁的线程的,只能有一个线程可以获取
  • EntryList: 关联没有抢到锁的线程,处于Blocked状态的线程
  • WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程

当一个线程尝试获取锁的时候,首先让该线程的对象和Monitor进行关联,就会判断Monitor结构中的Owner是否为null,如果为null,则持有Owner并获取锁

当后续的线程如果获取锁的时候,就会判断Owner是否为null,如果不为null,则加入EntryList集合中阻塞并进行等待Owner为null。注意EntryList不是队列,里面的线程谁先抢到Owner谁获取锁。

对于WaitSet集合,则是线程调用wait()方法就会加入该集合中 

3、 基础回答Sychronized底层原理概括

  • synchronized关键字的底层原理
  • Synchronized(对象锁)采用互斥的方式让同一时刻至多只有一个线程能持有(对象锁)
  • 它的底层由monitor实现的,monitor是jvm级别的对象 (c++实现),线程获得锁需要使用对象(锁)关联monitor
  • 在monitor内部有三个属性,分别是owner、 entrylist、 waitset:
    • 其中owner是关联的获得锁的线程,并且只能关联一个线程
    • entrylist关联的是处于阻塞状态的线程
    • waitset关联的是处于Waiting状态的线程

十、synchronized关键字的底层原理-进阶

1、Monitor实现的锁属于重量级锁,你了解过锁升级吗?

Monitor实现的锁属于重量级锁,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低

在JDK 1.6引入了两种新型锁机制:偏向锁和轻量级锁,它们的引入是为了解决在没有多线程竞争或基本没有竞争的场景下因使用传统锁机制带来的性能开销问题

前面已经讲到过了重量级锁,那么lock对象是如何和Monitor进行关联的呢?下面进行讲解

2、对象的内存结构

3、MarkWord

  • hashcode:25位的对象标识Hash码
  • age:对象分代年龄占4位
  • biased lock: 偏向锁标识,占1位,0表示没有开始偏向锁,1表示开启了偏向锁
  • thread: 持有偏向锁的线程ID,占23位
  • epoch: 偏向时间戳,占2位
  • ptr_to lock _record: 轻量级锁状态下,指向栈中锁记录的指针,占30位
  • ptr to heavyweight monitor: 重量级锁状态下,指向对象监视器Monitor的指针,占30位 

因此,对象头Mark Word里面由指向重量级锁的Monitor的指针ptr to heavyweight monitor。这个指针会指向Monitor的地址。因此lock对象根据对象头的重量级锁指针保存到Monitor地址来进行关联的。

4、Monitor重量级锁

每个Java 对象都可以关联一个Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的Mark Word 中就被设置指向 Monitor对象的指针 。

当多个线程发生竞争就会升级为重量级锁。一旦发生竞争就会升级为重量级锁。

5、轻量级锁

在很多的情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。因此JVM引入了轻量级锁的概念。

锁重入情况下使用轻量级锁

如上代码,当前对象由两个方法,分别为method1和method2。其中method1方法加锁,代码块内执行了method2方法。这就会出现同一个线程对同一个锁获取了两次,即锁重入。这种情况下可以使用轻量级锁,否则对性能会产生较大的影响。

根据以上代码,可以得知对象的内存结构。第一部分为MarkWord,第二部分为klass Word,第三部分为object body。其中MarkWord保存的是无锁状态下信息。

如果此刻来了一个线程,要执行method1的方法,在线程执行的时候就会创建一个锁记录,叫做Lock Record。

Lock Record由两个结构,分别为:

  • 锁记录:用来保存当前线程的轻量级锁的地址
  • Object reference:用来指向获取该锁的对象。

每个线程的栈帧包含着轻量级锁的结构,里面的锁记录会存储锁对象的MarkWord

首先会让线程栈帧里面的Lock Record中的Object reference指向Object对象,是为了记录当前线程正在获取的锁对象,表示这个线程锁获取的锁是这个对象,因为Sychronized里面的对象是Object,所以线程锁获取的锁就是这个Object对象。

上面还有个Lock record地址 00。当前线程持有锁的时候,就会去修改Object对象的MarkWord。这个交换根据CAS算法来进行交换Lock record地址和Object对象的MarkWord进行交换,用CAS为了保证修改交换的过程是原子操作。

如果交换成功了,Object对象的MarkWord就会改称为Lock record的地址,表示这个对象现在拥有轻量级锁。

如果CAS失败了,第一个原因是多个线程竞争锁,这个时候不能使用轻量级锁,会直接升级为重量级锁。第二个原因是当前锁重入。

比如method1中调用了method2方法,在已经加锁的前提下再加一层锁,那么就会在栈帧中添加一个Lock Record,作为重入的计数,比如Lock Record是几个就会算是几重锁。

加入两次锁,至少要进行两次CAS操作。每加一个锁记录都要加入一个CAS操作。因为第一次已经将轻量级锁地址记录到Object对象的MarkWord中,因此第二次Lock Record就不用真正的去修改了。并且第二个LockRecord的Object reference也会指向Object。

当解锁的时候,就会从栈顶开始向下遍历,查看Lock Record中的MarkWord是否为null。如果为null,说明该锁是重入锁,那么从栈中删除,并且锁计数减1并重置。

当解锁最后一个锁的时候,LockRecord中的锁记录就会和Object对象的MarkWord进行交换。就会变回Object中MarkWord初始状态,表示当前对象未上锁。

6、轻量级锁流程的概括

加锁流程

  • 在线程栈中创建一个Lock Record,将其obj字段指向锁对象
  • 通过CAS指令将Lock Record的地址存储在对象头的mark word中,如果对象处于无锁状态则修改成功,代表该线程获得了轻量级锁。
  • 如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一部分为null,起到了一个重入计数器的作用。
  • 如果CAS修改失败,说明发生了竞争,需要膨胀为重量级锁。

解锁流程 

  • 遍历线程栈找到所有obj字段等于当前锁对象的Lock Record。
  • 如果Lock Record的Mark Word为null,代表这是一次重入,将obj设置为null后continue。
  • 如果Lock Record的 Mark Word不为null,则利用CAS指令将对象头的mark word恢复成为无锁状态。如果失败则膨胀为重量级锁。

7、偏向锁

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS操作

Java 6中引入了偏向锁来做进一步优化: 只有第一次使用CAS 将线程ID设置到对象的Mark Word 头中,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS。以后只要不发生竞争,这个对象就归该线程所有。

以以上代码为例子如下:

对于Object对象MarkWord存储的数据,当线程1执行m1方法的时候,就会加锁。

因为当前线程是同一个线程,那么就会将当前线程Id写入Object中的MarkWord中,并且将偏向锁的标志改为1。

当执行m2方法的时候,就不会进行CAS操作,而是判断Object的MarkWord中的线程Id是否为当前线程Id,如果是那么就会将Lock Record加入栈帧中,并且设置锁记录为null,Object reference指向Object

8、Monitor实现的锁属于重量级锁,你了解过锁升级吗?

Java中的synchronized有偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。

 一旦锁发生了竞争,都会升级为重量级锁

当一个线程访问同步块时,它首先尝试获取偏向锁,如果偏向锁被占用,那么它会尝试获取轻量级锁,如果轻量级锁获取失败,那么它会尝试获取重量级锁

十一、谈谈JMM(JAVA内存模型)

  • JMM(Java Memory Model)Java内存模型,定义了共享内存中多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性
  • JMM把内存分为两块,一块是私有线程的工作区域(工作内存),一块是所有线程的共享区域(主内存)
  • 线程跟线程之间是相互隔离,线程跟线程交互需要通过主内存 

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

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

相关文章

PySpark基础入门(8):Spark SQL(内容补充)

目录 SparkSQL Shuffle 分区数目 SparkSQL 数据清洗API dropDuplicates dropna fillna SparkSQL函数定义(UDF函数) SparkSQL 使用窗口函数 SparkSQL运行流程 SparkSQL的自动优化 Catalyst优化器 SparkSQL Shuffle 分区数目 在SparkSQL中&…

无魔法插件 - ChatGPT Sidebar with GPT-4

文章目录 1.介绍2.功能一览2.1 唤醒方式2.2 聊天功能2.3 快捷模板2.4 单独聊天界面2.5 ChatPDF2.6 任意位置快捷使用模板2.7 手机 APP 3.GPT-3.0 还是 GPT-3.5?4.免费 or 收费?5.安装 Sidebar 创作不易,如果本文对你有帮助,胖友记…

SpringCloud(22):Sentinel对Feign的支持

Sentinel 适配了 Feign组件。如果想使用,除了引入 spring-cloud-starter-alibaba-sentinel 的依赖外还需要 2个步骤: 配置文件打开 Sentinel 对 Feign 的支持:feign.sentinel.enabledtrue加入 spring-cloud-starter-openfeign 依赖使 Sentin…

springboot 整合redis

第一步&#xff1a;pom.xml文件导入坐标 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.5.4</version> </dependency 第二步&#xff1a;appli…

【iOS】—— NSProxy类

NSProxy 文章目录 NSProxyNSProxy简介NSProxy模拟多继承NSProxy 避免NSTimer循环引用 在学消息转发的时候看到过这个类&#xff0c;本来没打算细看&#xff0c;后来看学长博客循环引用的时候也看到了这个类&#xff0c;就来细看看。 NSProxy简介 NSProxy 是一个实现了 NSObjec…

在线病毒分析工具评测试用

总览 1 区分在线杀毒引擎与在线沙盒 在线杀毒引擎用的大多是国际上出名的杀毒厂商的引擎作为底层&#xff0c;对文件进行扫描&#xff0c;结论通常会反馈“无毒”或者杀毒引擎自己的代码。 在线沙盒用云端的机器跑一次程序&#xff0c;然后收集程序的相关信息。 两者各有与…

情感分析讲解

情感分析简述 情感分析(Sentiment Analysis)又称倾向性分析&#xff0c;或意见挖掘&#xff0c;它是对带有情感色彩的主观性文本进行分析、处理、归纳和推理的过程。利用情感分析能力&#xff0c;可以针对带有主观描述的自然语言文本&#xff0c;自动判断该文本的情感正负倾向…

MongoDB 聚合操作Map-Reduce

这此之前已经对MongoDB中的一些聚合操作进行了详细的介绍&#xff0c;主要介绍了聚合方法和聚合管道&#xff1b;如果您想对聚合方法和聚合管道进行了解&#xff0c;可以参考&#xff1a; MongoDB 数据库操作汇总https://blog.csdn.net/m1729339749/article/details/130086022…

ClickHouse为何能超越Elasticsearch?

背景 Elasticsearch是一个强大的分布式全文检索和数据分析引擎&#xff0c;也是日志分析系统经常使用的一种实现方案&#xff0c;但近年来随着ClickHouse的发展&#xff0c;Elasticsearch在日志分析领域的地位逐渐被取代&#xff0c;许多公司已经将自己的日志分析解决方案从ES…

games101作业1

作业1的大致要求就是让我们实现如下两个函数&#xff0c;一个是返回在三维空间中绕着Z轴旋转的矩阵&#xff0c;另一个是返回投影矩阵。正确完成这两个函数之后&#xff0c;运行代码你就会在窗口中看到一个三角形&#xff0c;并且按a键和d键会发生旋转。 首先来实现get_model_m…

RuleApp1.4.0 文章社区客户端

简介&#xff1a; 可以打包成安卓&#xff0c;苹果&#xff0c;h5&#xff0c;小程序&#xff0c;全新的版本增加了私聊和群聊&#xff0c;动态模块等&#xff0c;还有自动和手动封禁机制。[滑稽][滑稽]主要模块&#xff1a;用户模块&#xff0c;文章模块&#xff0c;动态模块…

国产服务器tomcat开机自启

目录结构 前言方法一方法二方法三参考连接 前言 国产服务器配置tomcat开机自启动&#xff1b;目前测试两种服务器 银河麒麟&#xff08;Linux localhost.localdomain 4.19.90-52.22.v2207.ky10.x86_64 #1 SMP Tue Mar 14 12:19:10 CST 2023 x86_64 x86_64 x86_64 GNU/Linux&am…

多模态速读:ViLT、ALBEF、VLMO、BLIP

ViLT : Vision-and-Language Transformer Without Convolution or Region Supervision ViLT : Vision-and-Language Transformer Without Convolution or Region SupervisionIntroductionApproach参考 ALBEF: Vision and LanguageRepresentation Learning with Momentum Distil…

如何在香港服务器上进行网站迁移?五个主要步骤

​  服务器迁移是将大量关键信息从一台服务器移动到另一台服务器的过程&#xff0c;同时确保新服务器已正确配置以承载这些新信息。对于业务涉及中国大陆、香港及亚太区地区往来的用户&#xff0c;您可能需要将网站迁移到香港服务器上&#xff0c;来更好地发展业务。香港服务…

【c语言】字符串常用函数组件化封装 | 字符串总结

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c语言系列专栏&#xff1a;c语言之路重点知识整合 &#x…

【JavaScript】9.事件

事件 1. 注册事件&#xff08;绑定事件&#xff09; 给元素添加事件&#xff0c;称为注册事件或者绑定事件 1.1 注册事件两种方式 传统注册方式&#xff08;onclick&#xff09; 传统方式注册事件特点&#xff1a; 注册事件的唯一性同一个元素同一个事件只能设置一个处理函数…

离了大谱,公司测试岗却新来了个00后卷王,3个月薪资干到20K.....

最近聊到软件测试的行业内卷&#xff0c;越来越多的转行和大学生进入测试行业。想要获得更好的待遇和机会&#xff0c;不断提升自己的技能栈成了测试老人迫在眉睫的问题。 不论是面试哪个级别的测试工程师&#xff0c;面试官都会问一句“会编程吗&#xff1f;有没有自动化测试…

spring-web HandlerAdapter 源码分析

说明 本文基于 jdk 8, spring-framework 5.2.x 编写。author JellyfishMIX - github / blog.jellyfishmix.comLICENSE GPL-2.0 HandlerAdapter 接口 提供作为处理器适配器的能力。 supports 方法判断是否支持该 handler。 public interface HandlerAdapter {/*** 判断是否…

【跟着陈七一起学C语言】今天总结:初识C语言

友情链接&#xff1a;专栏地址 知识总结顺序参考C Primer Plus&#xff08;第六版&#xff09;和谭浩强老师的C程序设计&#xff08;第五版&#xff09;等&#xff0c;内容以书中为标准&#xff0c;同时参考其它各类书籍以及优质文章&#xff0c;以至减少知识点上的错误&#x…

Ansys Zemax | 设计抬头显示器时要使用哪些工具 – 第二部分

本文为使用OpticStudio工具设计优化HUD抬头显示器系统的第二部分&#xff0c;主要包含演示了如何使用OpticStudio工具设计分析抬头显示器&#xff08;HUD&#xff09;性能&#xff0c;即全视场像差&#xff08;FFA&#xff09;和NSC矢高图。&#xff08;联系我们获取文章附件&a…