java面试(多线程总结)

本文总结了Java多线程的相关知识,包括线程和进程的区别、并发与并行的概念、线程的生命周期和状态、死锁的规避、sleep()和wait()方法的区别、线程安全问题及其解决方案、synchronized关键字的使用、线程池的优势以及不同线程池的适用场景。内容详细,适合面试复习。
摘要由CSDN通过智能技术生成

线程和进程的基本定义

线程:是一个比进程更小的执行单位,能够完成进程中的一个功能,也被称为轻量级进程。一个进程在其执行的过程中可以产生多个线程。

【注】线程与进程不同的是:同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多

进程:在操作系统中能够独立运行,并且作为资源分配的基本单位。它表示运行中的程序。系统运行一个程序就是一个进程从创建、运行到消亡的过程。

  • 程序计数器为什么是私有的?

程序计数器主要有下面两个作用:

    1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
    2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

(需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。)

所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置

  •  虚拟机栈和本地方法栈为什么是私有的?
  • 虚拟机栈: 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程
  • 本地方法栈: 和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。

 

堆和方法区是所有线程共享的资源,其中:

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

 

并发与并行

并发指的是多个任务交替进行,并行则是指真正意义上的“同时进行”

  实际上,如果系统内只有一个CPU,使用多线程时,在真实系统环境下不能并行,只能通过切换时间片的方式交替进行,从而并发执行任务。真正的并行只能出现在拥有多个CPU的系统中

线程的生命周期和状态

Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:

  初始状态、运行状态、阻塞状态、等待状态、超时等待状态、终止状态

线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换

 

  • 线程创建之后它将处于 初始状态(NEW),调用 start() 方法后开始运行,线程这时候处于 可运行状态(READY)。
  • 可运行状态的线程获得了 CPU 时间片后就处于 运行状态(RUNNING)。
  • 线程执行 wait()方法之后,线程进入 等待状态(WAITING),进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态【notify()】。 而 超时等待状态(TIME_WAITING)相当于在等待状态的基础上增加了超时限制,【sleep(long millis)/wait(long millis)】,当超时时间到达后 Java 线程将会返回到运行状态
  • 当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态(BLOCKED)。
  • 线程在执行 Runnable 的run()方法之后将会进入到 终止状态(TERMINATED)。

 

线程的死锁以及如何规避

     多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

  假如线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。

避免死锁的几个常见方法:

  • 避免一个线程同时获取多个锁
  • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
  • 尝试使用定时锁,使用 lock.tryLock(timeout) 来代替使用内部锁机制。
  • 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

sleep() 方法和 wait() 方法区别和共同点

相同点:

  两者都可以暂停线程的执行,都会让线程进入等待状态。

不同点:

  • sleep()方法没有释放锁,而 wait()方法释放了锁。
  • sleep()方法属于Thread类的静态方法,作用于当前线程;而wait()方法是Object类的实例方法,作用于对象本身。
  • 执行sleep()方法后,可以通过超时或者调用interrupt()方法唤醒休眠中的线程;执行wait()方法后,通过调用notify()或notifyAll()方法唤醒等待线程。

为什么我们调用 start() 方法时会执行 run() 方法,而不能直接调用run()方法?

new 一个 Thread,线程进入初始状态;调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作

总结: 调用 start 方法可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

令人着迷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值