Java面试题冲刺第二十五天--并发编程2

目录

  • 面试题1:简单说下你对线程和进程的理解?
    • 正经回答:
    • 深入追问:
      • 追问1:那进程和线程有哪些区别呢?
  • 面试题2:守护线程和用户线程的区别? 正经回答:
    • 面试题3:什么是线程死锁?
      • 正经回答:
        • 深入追问:
          • 追问1:形成死锁的四个必要条件是什么?
          • 追问2:我们该如何避免死锁?
          • 追问3:死锁避免和死锁预防有啥不同?
      • 总结

        面试题1:简单说下你对线程和进程的理解?

        正经回答:
        Java面试题冲刺第二十五天--并发编程2
        文章图片

        进程
        • 一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。
        线程
        • 进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。
        【Java面试题冲刺第二十五天--并发编程2】

        深入追问:

        追问1:那进程和线程有哪些区别呢? 想起了某乎上的经典回答:
        做个简单的比喻:进程 → 火车,线程 → 车厢;线程在进程下行进(单纯的车厢无法运行)
        • 一个进程可以包含多个线程(一辆火车可以有多个车厢)
        • 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
        • 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
        • 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
        Java面试题冲刺第二十五天--并发编程2
        文章图片

        • 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
        • 进程可以拓展到多机,线程最多扩展到多核CPU,而不能扩展到多机(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
        • 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-“互斥锁”
        • 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”

        面试题2:守护线程和用户线程的区别? 正经回答: 用户 (User) 线程:
        • 运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程
        守护 (Daemon) 线程:
        • 运行在后台,为其他前台线程(非守护线程)服务。当所有用户线程都结束运行时,守护线程会随 JVM 一起结束工作.
        可见,守护线程是依赖于用户线程,当所有用户线程都退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程。
        而用户线程是独立存在的,不会因为其他用户线程退出而退出。
        注意事项:
        • setDaemon(true)必须在start()方法前执行,否则会抛出 IllegalThreadStateException 异常
        • 在守护线程中产生的新线程也是守护线程
        • 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
        • 守护 (Daemon) 线程中不能依靠 finally 块的内容来确保执行关闭或清理资源的逻辑。因为我们上面也说过了一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作,所以守护 (Daemon) 线程中的 finally 语句块可能无法被执行。

        面试题3:什么是线程死锁? 正经回答:
        死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。
        例如,在某个计算机系统中只有一台打印机和一台输入 设备,进程A正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程B 所占用,而B在未释放打印机之前,又提出请求使用正被A占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。
        多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
        如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
        Java面试题冲刺第二十五天--并发编程2
        文章图片

        下面是一个死锁示例代码:
        // 示例来自《并发编程之美》public class DeadLockDemo {private static Object resource1 = new Object(); //资源 1private static Object resource2 = new Object(); //资源 2public static void main(String[] args) {new Thread(() -> {synchronized (resource1) {System.out.println(Thread.currentThread() + "get resource1"); try {Thread.sleep(1000); } catch (InterruptedException e) {e.printStackTrace(); }System.out.println(Thread.currentThread() + "waiting get resource2"); synchronized (resource2) {System.out.println(Thread.currentThread() + "get resource2"); }}}, "线程 1").start(); new Thread(() -> {synchronized (resource2) {System.out.println(Thread.currentThread() + "get resource2"); try {Thread.sleep(1000); } catch (InterruptedException e) {e.printStackTrace(); }System.out.println(Thread.currentThread() + "waiting get resource1"); synchronized (resource1) {System.out.println(Thread.currentThread() + "get resource1"); }}}, "线程 2").start(); }}

        打印输出:
        Thread[线程 1,5,main]get resource1
        Thread[线程 2,5,main]get resource2
        Thread[线程 1,5,main]waiting get resource2
        Thread[线程 2,5,main]waiting get resource1

        线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过Thread.sleep(1000);让线程 A 休眠 1s 为的是让线程 B 得到CPU执行权,从而获取到 resource2 的监视器锁。
        线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。
        深入追问:

        追问1:形成死锁的四个必要条件是什么?
        • 互斥: 某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
        • 占有且等待: 一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
        • 不可抢占: 别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
        • 循环等待: 存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
        当以上四个条件均满足,必然会造成死锁,相反,而只要上述条件之一不满足,就不会发生死锁。
        发生死锁的进程无法进行下去,它们所持有的资源也无法释放。这样会导致CPU的吞吐量下降。所以死锁情况是会浪费系统资源和影响计算机的使用性能的。

        追问2:我们该如何避免死锁? 死锁避免的基本思想:系统对进程发出每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配。这是一种保证系统不进入死锁状态的动态策略。
        理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。只要打破四个必要条件之一就能有效预防死锁的发生:
        • 打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。
        • 打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。
        • 打破占有且等待条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。
        • 打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。

        追问3:死锁避免和死锁预防有啥不同? 死锁预防是设法至少破坏产生死锁的四个必要条件之一,严格的防止死锁的出现;而死锁避免则不那么严格的限制产生死锁的必要条件的存在,因为即使死锁的必要条件存在,也不一定发生死锁。死锁避免是在系统运行过程中注意避免死锁的最终发生。

        总结 本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

          推荐阅读