写点什么

Java 基础面试题【三】线程 (1)

作者:派大星
  • 2023-09-26
    辽宁
  • 本文字数:2387 字

    阅读完需:约 8 分钟

线程池的底层⼯作原理

线程池内部是通过队列+线程实现的,当我们利⽤线程池执⾏任务时:


  • 如果此时线程池中的线程数量⼩于 corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

  • 如果此时线程池中的线程数量等于 corePoolSize,但是缓冲队列 workQueue 未满,那么任务被放⼊缓冲队列。

  • 如果此时线程池中的线程数量⼤于等于 corePoolSize,缓冲队列 workQueue 满,并且线程池中的数量⼩于 maximumPoolSize,建新的线程来处理被添加的任务。

  • 如果此时线程池中的线程数量⼤于 corePoolSize,缓冲队列 workQueue 满,并且线程池中的数量等于 maximumPoolSize,那么通过 handler 所指定的策略来处理此任务。

  • 当线程池中的线程数量⼤于 corePoolSize 时,如果某线程空闲时间超过 keepAliveTime,线程将被终⽌。这样,线程池可以动态的调整池中的线程数

线程的生命周期?线程有几种状态

线程通常有五种状态,创建就绪运行阻塞死亡状态


  • 新建状态(New):新创建了一个线程对象。

  • 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的 start 方法。该状态的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权。

  • 运行状态(Running):就绪状态的线程获取了 CPU,执行程序代码。

  • 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

  • 死亡状态(Dead):线程执行完了或者因异常退出了 run 方法,该线程结束生命周期。


阻塞的情况又分为三种:

  1. 等待阻塞:运行的线程执行 wait 方法,该线程会释放占用的所有资源,JVM 会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用 notify 或 notifyAll 方法才能被唤醒,wait 是 object 类的方法

  2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入“锁池”中。

  3. 其他阻塞:运行的线程执行 sleep 或 join 方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep 状态超时、join 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。sleep 是 Thread 类的方法

sleep()、wait()、join()、yield()的区别

说到这里需要不得不提两个概念那就是锁池等待池


  • 锁池所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待 cpu 资源分配。

  • 等待池当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify()notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而 notifyAll()是将等待池的所有线程放到锁池当中


接着聊一下这几个方法的区别:


  • **sleep()**是 Thread 类的静态本地方法,**wait()**则是 Object 类的本地方法。

  • sleep()方法不会释放 lock,但是**wait()**会释放,而且会加入到等待队列中。


sleep 就是把 cpu 的执行资格和执行权释放出去,不再运行此线程,当定时时间结束再取回 cpu 资源,参与 cpu 的调度,获取到 cpu 资源后就可以继续运行了。而如果 sleep 时该线程有锁,那么 sleep 不会释放这个锁,而是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁。也就是说无法执行程序。如果在睡眠期间其他线程调用了这个线程的 interrupt 方法,那么这个线程也会抛出 interruptexception 异常返回,这点和 wait 是一样的。


  • sleep()方法不依赖于同步器 synchronized,但是 wait()需要依赖 synchronized 关键字。

  • **sleep()不需要被唤醒(休眠之后推出阻塞),但是 wait()**需要(不指定时间需要被别人中断)。

  • **sleep()**一般用于当前线程休眠,或者轮循暂停操作,**wait()**则多用于多线程之间的通信。

  • **sleep()会让出 CPU 执行时间且强制上下文切换,而 wait()**则不一定,**wait()**后可能还是有机会重新竞争到锁继续执行的。

  • **yield()**执行后线程直接进入就绪状态,马上释放了 cpu 的执行权,但是依然保留了 cpu 的执行资格,所以有可能 cpu 下次进行线程调度还会让这个线程获取到执行权继续执行

  • join()执行后线程进入阻塞状态,例如在线程 B 中调用线程 A 的 join(),那线程 B 会进入到阻塞队列,直到线程 A 结束或中断线程

对线程安全的理解

真正意义来讲并不是不是线程安全、应该是内存安全堆是共享内存,可以被所有线程访问通俗的来讲当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的


提到线程安全就不得不说 Java 的内存模型。


  • 众所周知堆是进程和线程共有的空间, 分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏。


需要注意的是在 Java 中,堆是 Java 虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。


  • 栈是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显式的分配和释放。


目前主流操作系统都是多任务的,即多个进程同时运行。为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的。在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。


如有问题,欢迎加微信交流:w714771310,备注- 技术交流  。或关注微信公众号【码上遇见你】。




发布于: 刚刚阅读数: 3
用户头像

派大星

关注

微信搜索【码上遇见你】,获取更多精彩内容 2021-12-13 加入

微信搜索【码上遇见你】,获取更多精彩内容

评论

发布
暂无评论
Java基础面试题【三】线程(1)_Java 面试题_派大星_InfoQ写作社区