面试 Java 并发编程八股文十问十答第二期
作者:程序员小白条,个人博客
相信看了本文后,对你的面试是有一定帮助的!关注专栏后就能收到持续更新!
⭐点赞⭐收藏⭐不迷路!⭐
1)形成死锁的四个必要条件是什么:
死锁是指多个线程或进程因相互竞争系统资源而陷入无限等待的状态。形成死锁的四个必要条件是:
- 互斥条件(Mutual Exclusion): 至少有一个资源是不可共享的,一次只能被一个线程占用。
- 请求与保持条件(Hold and Wait): 线程至少持有一个资源并请求其他线程占有的资源。
- 不可抢占条件(No Preemption): 系统不能抢占线程已经占有的资源,只能在线程自愿释放后才能被其他线程占用。
- 循环等待条件(Circular Wait): 多个线程形成循环等待资源的关系,每个线程都在等待下一个线程所占有的资源。
当这四个条件同时满足时,就会导致死锁的发生。
2)如何避免线程死锁:
避免线程死锁的方法主要包括以下几种:
- 避免使用多个锁: 尽量减少线程持有的锁的数量,避免多个锁之间互相依赖形成死锁。
- 按顺序获取锁: 规定线程获取锁的顺序,所有线程按照相同的顺序获取锁,可以避免循环等待的情况。
- 设置超时时间: 在获取锁时设置超时时间,如果超过一定时间无法获取到所需的锁,就主动释放已经获取的锁,避免长时间等待导致死锁。
- 死锁检测和处理: 建立死锁检测机制,及时发现死锁并采取相应的处理措施,如中断某个线程、回滚操作等。
- 资源分配图: 使用资源分配图等方法对资源的分配情况进行分析和规划,避免出现潜在的循环等待情况。
3)创建线程的四种方式:
在大多数编程语言和操作系统中,创建线程的方式主要有以下四种:
- 使用线程类或接口: 在面向对象的编程语言中,可以通过继承线程类或实现线程接口来创建线程,然后重写线程执行的逻辑。
- 使用线程库函数: 大多数操作系统提供了线程库函数,可以直接调用这些函数来创建线程,如
pthread_create()
函数用于在 POSIX 系统中创建线程。 - 使用线程池: 线程池是一种管理和复用线程的机制,可以提前创建一定数量的线程并放入线程池中,需要时从线程池中获取线程执行任务。
- 使用任务/执行器框架: 一些编程语言和框架提供了任务或执行器框架,可以将任务提交给执行器框架,由框架自动管理线程的创建和执行。
4)线程的 run() 和 start() 有什么区别?
- run() 方法:
run()
方法是线程类中定义的一个普通方法,用于线程的执行逻辑。当直接调用run()
方法时,其实是在当前线程的上下文中执行run()
方法的代码,而不会创建一个新的线程。 - start() 方法:
start()
方法是线程类继承自Thread
类或实现Runnable
接口后,调用Thread
对象的方法,用于启动一个新的线程。调用start()
方法会创建一个新的线程,并在新线程中执行run()
方法中定义的逻辑。
5)为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
- 当调用
start()
方法时,会创建一个新的线程并在新线程中执行run()
方法中定义的逻辑。这是因为start()
方法会启动线程的生命周期,包括线程的启动、运行、休眠、唤醒和终止等过程。 - 直接调用
run()
方法并不会创建一个新的线程,而是在当前线程的上下文中执行run()
方法的代码。这样做会导致逻辑在当前线程中串行执行,而不会实现多线程并发执行的效果。 - 因此,为了实现多线程并发执行,应该调用
start()
方法来启动新线程,让系统去调度新线程的执行,而不是直接调用run()
方法。
6)什么是 FutureTask?
FutureTask
是 Java 中 java.util.concurrent
包提供的一个类,实现了 RunnableFuture
接口,同时也实现了 Future
和 Runnable
接口,可以作为一个 Runnable
被提交给 Executor
执行,也可以作为一个 Future
被用来获取异步执行的结果。
FutureTask
可以用来包装一个 Callable
或 Runnable
对象,将其提交给线程池执行,并在将来的某个时刻获取执行结果。通过调用 get()
方法可以获取执行结果,如果任务还未完成,get()
方法会阻塞直到任务完成并返回结果。
FutureTask
提供了一种方便的方式来执行异步任务并获取结果,常用于多线程编程中需要获取异步任务执行结果的场景。
7)线程的状态和基本操作说说线程的生命周期及五种基本状态?
线程在 Java 中有以下五种基本状态:
- 新建状态(New): 当线程对象被创建但还未启动时,处于新建状态。
- 就绪状态(Runnable): 当线程对象被创建后,调用了
start()
方法,但还未获得 CPU 时间片时,处于就绪状态。 - 运行状态(Running): 当线程获得 CPU 时间片并开始执行时,处于运行状态。
- 阻塞状态(Blocked): 当线程被阻塞等待某个条件满足时,处于阻塞状态。
- 死亡状态(Terminated): 线程执行完任务或者异常终止时,处于死亡状态。
线程的生命周期包括:新建状态 -> 就绪状态 -> 运行状态 -> 阻塞状态 -> 运行状态 -> … -> 死亡状态。
8)Java 中用到的线程调度算法是什么?
Java 中使用的线程调度算法主要是抢占式调度算法。在抢占式调度中,操作系统会根据一定的策略(如优先级、时间片等)来决定当前哪个线程可以获得 CPU 时间片执行,当时间片用完或者发生阻塞等情况时,操作系统会重新调度其他线程执行。
9)线程的调度策略
线程的调度策略是操作系统根据一定的算法来决定哪个线程可以获得 CPU 时间片执行的策略。常见的调度策略包括:
- 抢占式调度(Preemptive Scheduling): 操作系统可以主动中断当前线程的执行,将 CPU 时间片分配给其他线程执行。
- 时间片轮转调度(Round-Robin Scheduling): 每个线程被分配一个时间片,当时间片用完后,操作系统将 CPU 时间片分配给下一个线程。
- 优先级调度(Priority Scheduling): 根据线程的优先级来决定哪个线程可以获得 CPU 时间片执行,优先级高的线程会被优先执行。
- 多级反馈队列调度(Multilevel Feedback Queue Scheduling): 根据线程的响应时间等因素动态调整线程的优先级,以提高系统的整体性能。
10)什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?
- 线程调度器(Thread Scheduler): 线程调度器是操作系统的一部分,负责决定在多个线程之间如何分配 CPU 时间片。线程调度器根据线程的优先级、调度策略等来决定哪个线程可以获得 CPU 时间片执行。
- 时间分片(Time Slicing): 时间分片是指操作系统将 CPU 时间分成若干个时间片段,每个线程被分配一个时间片段,当时间片用完后,线程会被暂停,操作系统会重新调度其他线程执行。时间分片可以保证每个线程都有机会执行,并避免某个线程长时间占用 CPU 导致其他线程无法执行的情况。
开源项目地址:https://gitee.com/falle22222n-leaves/vue_-book-manage-system
前后端总计已经 700+ Star,1W+ 访问!
⭐点赞⭐收藏⭐不迷路!⭐