面试 Java 并发编程八股文十问十答第十二期

发布于:2024-05-08 ⋅ 阅读:(24) ⋅ 点赞:(0)

面试 Java 并发编程八股文十问十答第十二期

作者:程序员小白条个人博客

相信看了本文后,对你的面试是有一定帮助的!关注专栏后就能收到持续更新!

⭐点赞⭐收藏⭐不迷路!⭐

1)线程安全:

线程安全指的是当多个线程同时访问某个资源时,不会产生不正确的结果。具体来说,线程安全的代码在多线程环境下能够保证数据的一致性、正确性和可预期的行为,不会出现竞态条件、数据竞争或者其他并发问题。线程安全的实现通常包括使用同步机制(如锁)、原子操作、并发数据结构等手段来确保共享资源的访问不会导致数据不一致或损坏。

2)线程和进程的区别:

线程和进程都是操作系统中的执行单元,但它们之间有一些重要的区别:

  • 进程是程序执行时的一个实例,拥有独立的内存空间和系统资源,可以包含多个线程。
  • 线程是进程中的一个执行路径,共享了进程的内存空间和资源,可以看作是进程中的子任务。一个进程可以包含多个线程,这些线程共享进程的地址空间和全局变量,但拥有各自的栈空间和局部变量。

简而言之,进程是资源分配的最小单位,而线程是 CPU 调度的最小单位。

3)协程:

协程是一种轻量级的线程替代方案,也被称为用户级线程或者绿色线程。与传统的线程相比,协程更加轻量级,因为它们由用户空间的库或者语言运行时管理,而不需要操作系统内核的支持。协程可以在单个线程中实现并发执行,通过在不同的协程之间进行切换来实现并发性,而不是通过线程的上下文切换。协程通常用于处理高并发、高吞吐量的场景,例如网络编程、IO 密集型任务等。

4)线程的生命周期:

线程的生命周期通常包括以下几个状态:

  1. 新建状态(New):线程被创建但尚未启动。
  2. 就绪状态(Runnable):线程已经被创建并且调用了 start() 方法,但是尚未被分配到 CPU 执行。
  3. 运行状态(Running):线程被分配到 CPU 执行,正在执行任务。
  4. 阻塞状态(Blocked):线程被挂起,通常是由于等待某个条件的发生,比如等待 I/O 操作完成、等待获取锁、等待其他线程的通知等。
  5. 等待/无限期等待状态(Waiting/Timed Waiting):线程等待某个特定条件的发生,等待的时间可能有限(Timed Waiting)也可能是无限期(Waiting)的。
  6. 终止状态(Terminated):线程执行完成或者因异常而终止。

5)线程之间的通信:

线程之间的通信可以通过共享内存或者消息传递来实现:

  • 共享内存:多个线程访问同一个共享变量或者内存区域,通过读写共享内存来进行通信。这种方式的优点是简单高效,但容易产生竞态条件和数据一致性问题,需要使用同步机制来保证线程安全。
  • 消息传递:线程之间通过发送和接收消息来进行通信,每个线程都有自己的独立内存空间,通过消息队列或者信号量等机制来实现线程间的数据传递和同步。这种方式的优点是避免了竞态条件和数据一致性问题,但需要额外的同步开销。

Java 中线程之间的通信可以通过 wait()、notify()、notifyAll() 方法来实现等待和唤醒机制,或者使用并发工具类如 CountDownLatch、CyclicBarrier、Semaphore 等来进行线程间的协调和通信。

6)进程之间的通信:

进程之间的通信通常通过以下几种方式来实现:

  • 管道(Pipe):一种半双工的通信方式,允许一个进程写入数据到管道,另一个进程从管道读取数据。在 Unix/Linux 系统中,管道通常通过 | 运算符来连接两个命令的标准输入和输出。
  • 信号(Signal):进程可以向另一个进程发送信号,通知其发生了某种事件。比如 Unix/Linux 系统中的 kill 命令可以向指定进程发送信号。
  • 消息队列(Message Queue):进程之间通过消息队列来传递数据,发送进程将消息写入队列,接收进程从队列中读取消息。消息队列通常由操作系统提供支持。
  • 共享内存(Shared Memory):多个进程共享同一块内存区域,可以直接读写共享内存来进行通信。但需要通过同步机制来保证数据的一致性和安全性。
  • 套接字(Socket):进程之间通过套接字进行网络通信,可以在同一台计算机上的不同进程之间进行通信,也可以在不同计算机上的进程之间进行通信。套接字通常用于网络编程和 IPC(进程间通信)。

选择哪种通信方式取决于具体的需求和环境,每种方式都有自己的特点和适用场景。

7)线程池的原理:

线程池是一种管理和复用线程的机制,通过预先创建一定数量的线程并维护一个线程队列,来执行提交的任务。线程池的原理主要包括以下几个关键点:

  • 线程复用:线程池会创建一定数量的线程,并将这些线程保存在线程池中,每当有任务提交时,线程池会选择一个空闲的线程来执行任务,从而避免了频繁地创建和销毁线程的开销。
  • 线程管理:线程池负责管理线程的生命周期,包括线程的创建、启动、执行任务、等待任务、关闭等操作。
  • 任务队列:线程池通常会维护一个任务队列,用于保存提交的任务。当线程池中的线程处于忙碌状态时,新的任务会被放入任务队列中等待执行。
  • 线程调度:线程池会负责调度任务的执行,根据任务的类型、优先级等因素来决定任务的执行顺序。
  • 资源限制:线程池可以根据系统资源的情况来限制线程的数量,避免线程过多导致资源耗尽或者系统负载过重。

8)设计线程池的步骤:

设计线程池需要考虑以下几个方面:

  1. 确定线程池的大小:根据系统资源、任务类型和性能需求等因素来确定线程池的大小,一般建议根据实际情况进行测试和调整。
  2. 选择合适的任务队列:选择合适的任务队列来保存提交的任务,常见的任务队列包括有界队列(如 ArrayBlockingQueue)和无界队列(如 LinkedBlockingQueue)。
  3. 定义线程池的拒绝策略:在任务提交过多或者线程池已满无法处理新任务时,需要定义一种合适的拒绝策略来处理这种情况,常见的拒绝策略包括丢弃任务、抛出异常、阻塞等待和调用者执行等。
  4. 实现线程池的执行逻辑:实现线程池的核心逻辑,包括任务提交、线程分配、任务执行、异常处理、线程池关闭等操作。
  5. 监控和调优:添加监控和调优功能,可以实时监控线程池的状态、任务执行情况和性能指标,并根据监控结果进行调整和优化。

9)线程池的线程数设置:

线程池的线程数设置应该根据系统资源、任务类型和性能需求等因素来确定,一般可以考虑以下几个原则:

  • 任务类型:CPU 密集型任务通常需要较少的线程数,而 IO 密集型任务则可以使用较多的线程数。
  • 系统资源:根据系统的 CPU 核数、内存大小和其他资源情况来确定线程池的大小,避免线程过多导致资源竞争和系统负载过重。
  • 性能需求:根据任务的响应时间和吞吐量需求来确定线程池的大小,一般通过压测和性能调优来确定合适的线程数。

一种常见的做法是通过公式或者根据经验设置线程池的大小,然后根据实际情况进行测试和调整,以达到最优的性能和资源利用率。

10)线程池的拒绝策略:

当线程池无法处理新提交的任务时,需要采取一种拒绝策略来处理这种情况,常见的拒绝策略包括:

  1. AbortPolicy(默认策略):直接抛出 RejectedExecutionException 异常,表示拒绝执行新任务。
  2. CallerRunsPolicy:将任务交给调用线程来执行,如果线程池已满,则由提交任务的线程来执行该任务。
  3. DiscardPolicy:直接丢弃新提交的任务,不做任何处理。
  4. DiscardOldestPolicy:丢弃任务队列中最旧的任务,然后尝试将新任务加入队列。

开源项目地址:https://gitee.com/falle22222n-leaves/vue_-book-manage-system

前后端总计已经 800+ Star,1.5W+ 访问!

⭐点赞⭐收藏⭐不迷路!⭐


网站公告

今日签到

点亮在社区的每一天
去签到