面试 Java 并发编程八股文十问十答第九期
作者:程序员小白条,个人博客
相信看了本文后,对你的面试是有一定帮助的!关注专栏后就能收到持续更新!
⭐点赞⭐收藏⭐不迷路!⭐
1)ThreadLocal造成内存泄漏的原因?
ThreadLocal 造成内存泄漏的主要原因是在使用完 ThreadLocal 后没有及时清理对应的线程副本。当线程结束后,如果没有手动清理 ThreadLocal 对应的值,那么该值将会一直存在于内存中,不会被垃圾回收器回收,从而导致内存泄漏。
具体来说,当一个线程结束时,如果 ThreadLocalMap 中的 Entry 对象没有被及时清理,其中的 value 对象将无法被回收,从而造成内存泄漏。
2)ThreadLocal内存泄漏解决方案?
为了避免 ThreadLocal 的内存泄漏,可以采取以下解决方案:
- 及时清理:在使用完 ThreadLocal 后,手动调用 remove() 方法清理对应的线程副本。可以使用 try-finally 语句块来确保在任何情况下都能进行清理操作。
- 使用弱引用:可以使用弱引用来存储 ThreadLocal 对象,这样当线程结束时,ThreadLocal 对象将会被垃圾回收器回收,从而自动清理线程副本。可以使用 InheritableThreadLocal 类来代替 ThreadLocal,因为它使用了弱引用来存储 ThreadLocal 对象。
- 使用线程池:在使用线程池时,应该及时清理 ThreadLocal 的值,以避免线程重用时出现副本的问题。
3)什么是阻塞队列?阻塞队列的实现原理是什么?如何使用阻塞队列来实现生产者-消费者模型?
阻塞队列的实现原理是通过使用锁和条件变量来实现的。当队列为空时,从队列中获取元素的操作会进入等待状态,直到有新的元素被添加到队列中,并通知等待的线程;当队列已满时,往队列中添加元素的操作会进入等待状态,直到有空位被释放,并通知等待的线程。
阻塞队列可以用来实现生产者-消费者模型,其中生产者线程负责往队列中添加元素,消费者线程负责从队列中获取元素。生产者线程和消费者线程可以并发地操作队列,而不需要显式地进行同步操作。阻塞队列提供了一种简单而高效的方式来实现线程间的通信和协作。常见的阻塞队列实现包括 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 等。
4)什么是线程池?有哪几种创建方式?
线程池是一种管理和复用线程的机制,它通过预先创建一定数量的线程,并将任务提交给这些线程来执行,从而避免了频繁创建和销毁线程的开销。线程池可以根据需要动态地调整线程数量,以适应不同的工作负载。
Java 中的线程池可以通过 ThreadPoolExecutor 类来创建和管理。常见的线程池创建方式包括:
- 使用 Executors 工具类的静态方法创建线程池,如 Executors.newFixedThreadPool()、Executors.newCachedThreadPool() 等。
- 使用 ThreadPoolExecutor 类的构造函数自定义线程池的参数,如核心线程数、最大线程数、线程空闲时间等。
5)线程池有什么优点?
线程池具有以下优点:
- 提高性能:线程池可以重复利用已创建的线程,避免了线程创建和销毁的开销,提高了程序的性能。
- 提高响应性:线程池可以根据需要动态调整线程数量,确保任务能够及时得到处理,提高了系统的响应性。
- 提供线程管理和监控:线程池提供了对线程的管理和监控功能,可以方便地控制线程的数量、状态和执行优先级,以及监控线程的运行情况。
- 控制资源占用:线程池可以限制线程的数量,避免过多的线程占用系统资源,从而提高系统的稳定性和可靠性。
6)线程池都有哪些状态?
Java 中的线程池有以下几种状态:
- RUNNING:线程池处于运行状态,可以接受新的任务,并处理已提交的任务。
- SHUTDOWN:线程池处于关闭状态,不再接受新的任务,但会处理已提交的任务。
- STOP:线程池处于停止状态,不再接受新的任务,也不会处理已提交的任务,并且会中断正在执行的任务。
- TIDYING:线程池正在进行状态转换,例如在 SHUTDOWN 状态下,当所有任务都已完成时,会转换为 TIDYING 状态。
- TERMINATED:线程池已经终止,不再接受新的任务,也不会处理已提交的任务。
线程池的状态转换是有顺序的,一般是从 RUNNING 状态开始,经过 SHUTDOWN、TIDYING 最终到达 TERMINATED 状态。
7)什么是 Executor 框架?为什么使用 Executor 框架?
Executor 框架是 Java 中用于管理和执行线程任务的框架。它提供了一种简单、灵活和可扩展的方式来创建和管理线程,以及处理并发任务。
使用 Executor 框架的好处包括:
- 提供了线程池的管理和复用机制,避免了频繁创建和销毁线程的开销。
- 可以根据需要动态调整线程数量,以适应不同的工作负载。
- 可以提高程序的性能和响应性,确保任务能够及时得到处理。
- 提供了线程管理和监控功能,方便控制线程的数量、状态和执行优先级,以及监控线程的运行情况。
- 控制资源占用,避免过多的线程占用系统资源,提高系统的稳定性和可靠性。
8)在 Java 中 Executor 和 Executors 的区别?
Executor 是一个接口,定义了执行任务的方法,主要包括 execute() 方法用于提交任务。
Executors 是一个工具类,提供了一些静态方法来创建和管理线程池。它封装了 Executor 接口的实现,提供了一些常用的线程池创建方式,如 newFixedThreadPool()、newCachedThreadPool() 等。
可以说 Executors 是 Executor 的一个实现和扩展,它提供了更方便的方式来创建和管理线程池。
9)线程池中 submit() 和 execute() 方法有什么区别?
线程池中的 submit() 和 execute() 方法都用于提交任务给线程池执行,但它们有一些区别:
- execute() 方法只能接受 Runnable 对象,而 submit() 方法既可以接受 Runnable 对象,也可以接受 Callable 对象。
- execute() 方法没有返回值,而 submit() 方法返回一个 Future 对象,可以用于获取任务的执行结果。
- 对于抛出异常的任务,execute() 方法会在控制台打印异常堆栈信息,而 submit() 方法会把异常包装在 Future 对象中,需要通过调用 Future 的 get() 方法来获取异常。
一般来说,如果不需要获取任务的执行结果,可以使用 execute() 方法;如果需要获取任务的执行结果或捕获任务抛出的异常,可以使用 submit() 方法。
10)什么是线程组,为什么在 Java 中不推荐使用?
线程组是 Java 中用于对线程进行分组管理的机制。线程组可以将一组线程作为一个单元进行管理,可以统一设置线程组的优先级、守护状态等属性,也可以对线程组中的线程进行批量操作。
然而,在 Java 中,线程组并不被推荐使用。主要原因如下:
- 线程组的功能有限,无法提供足够的灵活性和扩展性,不能满足复杂的线程管理需求。
- 线程组的使用会增加代码的复杂性,容易引入混乱和错误。
- 线程组并不是一种好的线程管理和调度机制,更推荐使用 Executor 框架来管理和执行线程任务。
因此,尽管线程组在某些特定场景下可能有一定的用处,但在一般情况下,应该避免使用线程组,而选择更为灵活和可扩展的线程管理方式。
开源项目地址:https://gitee.com/falle22222n-leaves/vue_-book-manage-system
前后端总计已经 800+ Star,1.5W+ 访问!
⭐点赞⭐收藏⭐不迷路!⭐