多线程与高并发常见面试题(1)

长按扫码关注,分享互联网公司关注的技术栈

file

1.线程的创建几种方法:
  • 实现 Runnable 接口
  • 继承Thread类。
  • 线程池创建线程。
  • 有返回值的 Callable 创建线程
  • 其他创建方式 定时器 Timer。
  • 其他创建方法:匿名内部类,lambda 表达式。
2.为什么实现 Runnable 接口比继承 Thread 类实现线程要好?
  • 1.首先,我们从代码的架构考虑,实际上,Runnable 里只有一个 run() 方法,它定义了需要执行的内容,在这种情况下,实现了 Runnable 与 Thread 类的解耦,Thread 类负责线程启动和属性设置等内容,权责分明。
  • 2.性能角度:Thread 每次需要维护线程的生命周期,从创建到销毁的过程。Runnable 可以放到线程池中使用,降低性能开销。
  • 3.代码扩展性:Java 语言不支持双继承,如果我们的类一旦继承了 Thread 类,那么它后续就没有办法再继承其他的类,这样一来,如果未来这个类需要继承其他类实现一些功能上的拓展,它就没有办法做到了,相当于限制了代码未来的可拓展性。
3.如何正确停止线程?为什么 volatile 标记位的停止方法是错误的?
  • 通常情况下,我们不会手动停止一个线程,而是允许线程运行到结束,然后让它自然停止。但是依然会有许多特殊的情况需要我们提前停止线程,比如:用户突然关闭程序,或程序运行出错重启等。

  • 在这种情况下,即将停止的线程在很多业务场景下仍然很有价值。尤其是我们想写一个健壮性很好,能够安全应对各种场景的程序时,正确停止线程就显得格外重要。但是Java 并没有提供简单易用,能够直接安全停止线程的能力。

  • 对于 Java 而言,最正确的停止线程的方式是使用 interrupt。但 interrupt 仅仅起到通知被停止线程的作用。而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。

  • Thread.sleep(1000000); 可以使用 thread.interrupt(); 进行中断。触发后,处于休眠中的线程被中断,那么线程是可以感受到中断信号的,并且会抛出一个 InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。这样一来就不用担心长时间休眠中线程感受不到中断了,因为即便线程还在休眠,仍然能够响应中断通知,并抛出异常。

  • 对于抛出的异常,应该向上层上报,而不是自己吞并。异常交由调用方处理。还可以在catch语句中再次中断线程。因为如果线程在休眠期间被中断,那么会自动清除中断信号。如果这时手动添加中断信号,中断信号依然可以被捕捉到。这样后续执行的方法依然可以检测到这里发生过中断,可以做出相应的处理,整个线程可以正常退出。

  • volatile 这种方法在某些特殊的情况下,比如线程被长时间阻塞的情况,就无法及时感受中断,所以 volatile 是不够全面的停止线程的方法。

4.线程是如何在 6 种状态之间转换的?
  • 线程的 6 种状态,就像生物从出生到长大、最终死亡的过程一样,线程也有自己的生命周期,在 Java 中线程的生命周期中一共有 6 种状态。
  • New(新创建)
  • Runnable(可运行)
  • Blocked(被阻塞)
  • Waiting(等待)
  • Timed Waiting(计时等待)
  • Terminated(被终止)
  • 如果想要确定线程当前的状态,可以通过 getState() 方法,并且线程在任何时刻只可能处于 1 种状态。
    -状态流转:
    file
5.一共有哪 3 类线程安全问题?
  • 1.运行结果错误:i++问题。
  • 2.发布和初始化导致线程安全问题
  • 3.活跃性问题:最典型的有三种,分别为死锁、活锁和饥饿。
6.哪些场景需要额外注意线程安全问题?
  • 1.访问共享变量或资源,典型的场景有访问共享对象的属性,访问 static 静态变量,访问共享的缓存,等等。
  • 2.依赖时序的操作:如果我们操作的正确性是依赖时序的,而在多线程的情况下又不能保障执行的顺序和我们预想的一致,这个时候就会发生线程安全问题
  • 3.不同数据之间存在绑定关系:第三种需要我们注意的线程安全场景是不同数据之间存在相互绑定关系的情况。有时候,我们的不同数据之间是成组出现的,存在着相互对应或绑定的关系,最典型的就是 IP 和端口号。有时候我们更换了 IP,往往需要同时更换端口号,如果没有把这两个操作绑定在一起,就有可能出现单独更换了 IP 或端口号的情况,而此时信息如果已经对外发布,信息获取方就有可能获取一个错误的 IP 与端口绑定情况,这时就发生了线程安全问题。在这种情况下,我们也同样需要保障操作的原子性。
  • 4.对方没有声明自己是线程安全的:第四种值得注意的场景是在我们使用其他类时,如果对方没有声明自己是线程安全的,那么这种情况下对其他类进行多线程的并发操作,就有可能会发生线程安全问题。举个例子,比如说我们定义了 ArrayList,它本身并不是线程安全的,如果此时多个线程同时对 ArrayList 进行并发读/写,那么就有可能会产生线程安全问题,造成数据出错,而这个责任并不在 ArrayList,因为它本身并不是并发安全的。
7.为什么多线程会带来性能问题?
  • 调度开销
  • 上下文切换
  • 缓存失效
  • 协作开销
8.使用线程池比手动创建线程好在哪里?
  • 1.线程池可以解决线程生命周期的系统开销问题,同时还可以加快响应速度。因为线程池中的线程是可以复用的,我们只用少量的线程去执行大量的任务,这就大大减小了线程生命周期的开销。而且线程通常不是等接到任务后再临时创建,而是已经创建好时刻准备执行任务,这样就消除了线程创建所带来的延迟,提升了响应速度,增强了用户体验。
  • 2.线程池可以统筹内存和CPU 的使用,避免资源使用不当。线程池会根据配置和任务数量灵活地控制线程数量,不够的时候就创建,太多的时候就回收,避免线程过多导致内存溢出,或线程太少导致 CPU 资源浪费,达到了一个完美的平衡。
  • 3.线程池可以统一管理资源。比如线程池可以统一管理任务队列和线程,可以统一开始或结束任务,比单个线程逐一处理任务要更方便、更易于管理,同时也有利于数据统计,比如我们可以很方便地统计出已经执行过的任务的数量。

本文由博客群发一文多发等运营工具平台 OpenWrite 发布

原文地址:https://www.cnblogs.com/fhychzu/p/14098438.html