基础

  1. 进程和线程的区别

image.png
同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器虚拟机栈本地方法栈,所以生成一个线程或者在线程间切换的开销比进程小得多,因此也被称为轻量级进程。

堆是进程中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

线程和进程最大的不同在于基本上各进程是独立的,不会相互影响

回答区别时从两个点:1.开销 2.独立性

JMX可以用来查看Java程序有哪些线程

  1. public class MultiThread {
  2. public static void main(String[] args) {
  3. // 获取 Java 线程管理 MXBean
  4. ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
  5. // 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
  6. ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
  7. // 遍历线程信息,仅打印线程 ID 和线程名称信息
  8. for (ThreadInfo threadInfo : threadInfos) {
  9. System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
  10. }
  11. }
  12. }
  1. 程序计数器 虚拟机栈和本地方法栈的作用?为什么要是私有的?
    1. 程序计数器
      1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现流程控制,例如顺序、选择、循环、异常处理
      2. 多线程情况下通过计数器来记录当前线程执行的位置,切换回来时才能知道之前执行到哪里了
    2. 虚拟机栈:每个Java方法 在执行的同时会创建一个栈帧,存储局部变量标、操作数栈、常量池引用信息。虚拟机栈为虚拟机执行Java方法(字节码)提供服务
    3. 本地方法栈:为虚拟机使用到的Native方法服务,在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
  2. 线程的生命周期和状态

Java并发编程 - 图2
Java并发编程 - 图3
在操作系统中层面线程有 READY 和 RUNNING 状态,而在 JVM 层面只能看到 RUNNABLE 状态
为什么 JVM 没有区分这两种状态呢?
现在的时分(time-sharing)多任务(multi-task)操作系统架构通常都是用所谓的“时间分片(time quantum or time slice)”方式进行抢占式(preemptive)轮转调度(round-robin式)。这个时间分片通常是很小的,一个线程一次最多只能在 CPU 上运行比如 10-20ms 的时间(此时处于 running 状态),也即大概只有 0.01 秒这一量级,时间片用后就要被切换下来放入调度队列的末尾等待再次调度。(也即回到 ready 状态)。线程切换的如此之快,区分这两种状态就没什么意义了。
Java并发编程 - 图4

  1. 什么时候发生线程切换?

    1. 主动让出,如调用wait(),sleep()
    2. 时间片用完(操作系统防止其他线程或进程饿死)
    3. 调用了阻塞类型的系统中断,如请求IO,线程被阻塞
    4. 被终止或者结束运行
  2. 死锁是什么?如何避免?

死锁:两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
产生死锁的四个条件:

  1. 互斥条件:线程对于所分配到的资源具有排它性,该资源任意一个时刻只由一个线程占用。
  2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
  4. 循环等待条件:发生死锁时,若干线程之间形成一个环路。

如何预防:破坏必要条件

  1. 破坏请求与保持:一次性申请所以资源
  2. 破坏不剥夺:申请不到时可以让进程主动释放占有的资源
  3. 破坏循环等待:资源有序分配法。系统为每种资源分配一个编号,进程在申请时必须按照编号顺序申请,释放时则反序释放

如何避免:在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。安全状态是指至少有一个资源分配序列不会导致死锁,这些序列被成为安全序列。

  1. sleep() 和 wait() 的区别和共同点

共同点:都可以暂停线程的执行

sleep() wait()
释放锁 没有释放锁 释放锁
用途 暂停执行 线程间交互/通信
苏醒 方法执行完成后,线程自动苏醒 线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法
  1. 为什么调用start()方法时会执行run()方法,而不直接调用run()方法?

new 一个 Thread,线程进入了新建状态。
调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。

但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

synchronized 关键字

  1. 作用:保证被synchronized关键字修饰的方法或代码块在任意时刻只能有一个线程执行