Java并发常见面试题(上)

Java并发常见面试题(上)

什么是线程和进程?

一个 Java 程序的运行是 main 线程和多个其他线程同时运行

进程:程序的一次执行过程,系统运行一个程序就是一个进程从创建,运行到消亡的过程。在Java中启动main函数就是开启一个进程,main函数所在线程就是主线程

线程 :一个进程可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区 。每个线程有自己的程序计数器虚拟机和本地方法栈

Java进程和操作系统的进程有啥区别?

JDK1.2之前Java是绿色线程,也就是自己模拟多线程运行不依赖操作系统,但是绿色线程不能使用操作系统提供的功能(异步IO)且无法利用多核.在之后就改为原生线程

  • 用户线程:由用户空间程序管理和调度的线程,运行在用户空间(专门给应用程序使用)。
  • 内核线程:由操作系统内核管理和调度的线程,运行在内核空间(只有内核程序可以访问)

现在的 Java 线程的本质其实就是操作系统的线程

线程和进程关系和区别

一个进程有多个线程,多个线程共享进程的堆和方法区 。每个线程有自己的程序计数器虚拟机栈本地方法栈

为什么程序计数器,虚拟机栈和本地方法栈是私有的?

程序计数器是通过解释器来控制代码执行,为了方便切换线程后恢复正确执行位置所以是私有的

虚拟机栈存放方法创建的局部变量,常量池等。本地方法栈为虚拟机执行的native方法服务

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

了解堆和方法区

堆存放新建的对象,方法区存放类信息,常量,静态变量等数据

如何创建线程?

一般来说,创建线程有很多种方式,例如继承Thread类、实现Runnable接口、实现Callable接口、使用线程池、使用CompletableFuture类等等。

严格来说,Java 就只有一种方式可以创建线程,那就是通过new Thread().start()创建。不管是哪种方式,最终还是依赖于new Thread().start()

线程生命周期和状态

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

什么是线程上下文切换

线程切换需要保存当前线程的上下文

线程在执行过程中有自己的运行条件和状态(上下文)

线程上下文切换就是保留此线程的上下文,加载下一个占用cpu的上下文

Thread.sleep和wait方法的区别

共同点:两者使线程暂停

区别:

  • sleep方法没有释放锁,wait释放锁
  • wait方法用于切换线程,sleep用于暂停执行
  • wait方法苏醒需要notify或者notifyall方法,sleep执行后自动苏醒
  • sleep是Thread的静态本地方法,wait是object的本地方法

为什么wait在Object中?

因为它需要得到对象锁,而不是线程

sleep就没有关于对象的操作所以在Thread中

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

new 一个Thread,线程进入准备状态,调用start方法会启动一个线程进入就绪状态分配到时间片就开始执行了,会自动调用run方法

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

并发与并行的区别

并发:同一时间段执行任务

并行:同一时刻之星任务

同步和异步的区别

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

为什么要使用多线程?

多核时代减少了线程上下文切换的开销,多线程机制可以提高性能

单核CPU支持多线程

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

系统调度方式

  • 抢占式调度:系统时钟中断(时间片轮转)或其他高优先级事件(如 I/O 操作完成)触发的。存在上下文开销,不易阻塞
  • 协同式调度:线程结束后开始下一个线程,减少了上下文开销,容易阻塞

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

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

两种情况:

  1. CPU集密型:占用大部分CPU资源,降低了效率
  2. IO集密型:大量IO操作,不占用CPU资源,提高了效率

多线程带来的问题

内存泄漏,死锁,线程不安全

如何理解线程不安全和安全

对同一份数据的访问能否保证正确性和一致性

什么是线程死锁

多个线程等待一个资源被释放,但是这个资源被无限期地阻塞了

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

死锁的四个必要条件:

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

如何检测死锁

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

如何预防和避免死锁

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

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

如何避免死锁?

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

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

相关推荐
所待.3837 分钟前
JavaEE之线程初阶(上)
java·java-ee
Winston Wood11 分钟前
Java线程池详解
java·线程池·多线程·性能
手握风云-15 分钟前
数据结构(Java版)第二期:包装类和泛型
java·开发语言·数据结构
喵叔哟35 分钟前
重构代码中引入外部方法和引入本地扩展的区别
java·开发语言·重构
尘浮生41 分钟前
Java项目实战II基于微信小程序的电影院买票选座系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
不是二师兄的八戒1 小时前
本地 PHP 和 Java 开发环境 Docker 化与配置开机自启
java·docker·php
爱编程的小生1 小时前
Easyexcel(2-文件读取)
java·excel
带多刺的玫瑰2 小时前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法
计算机毕设指导62 小时前
基于 SpringBoot 的作业管理系统【附源码】
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
Gu Gu Study2 小时前
枚举与lambda表达式,枚举实现单例模式为什么是安全的,lambda表达式与函数式接口的小九九~
java·开发语言