多线程面试题

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

线程从创建到工作到死亡的过程称为线程的生命周期。

总共是7个状态,在Thread.State枚举类中。

  • 新建状态(New)
  • 就绪状态(Runnable)
  • 运行状态(Running)
  • 阻塞状态(Blocked)
  • 等待状态(Waiting)
  • 超时等待状态(Timed Waiting)
  • 终止状态(Terminated)

2.关于sleep,wait,yield,join的区别

  • sleep()方法
    1. 在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”.不推荐使用。
    2. sleep()使当前线程进入阻塞状态,在指定时间内不会执行。
  • wait()方法
    1. 在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。
    2. 当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
    3. 唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。
    4. wait()和notify()必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。
  • yield()方法
    1. 暂停当前正在执行的线程对象。
    2. yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
    3. yield()只能使同优先级或更高优先级的线程有执行的机会。
    4. 调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
  • join()方法
    1. 等待该线程终止。
    2. 等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。
    3. 在很多情况下,主线程创建并启动了线程,如果子线程中要进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。

3.对线程安全的理解

线程安全:

线程安全是编程中的术语,指某个函数、函数库在并发环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。

并发与并行(大白话)

并发是两个队伍交替使用一台咖啡机。并行是两个队伍同时使用两台咖啡机。

4.Thread和Runnable区别

  1. Thread是一个类(单一继承),Runnable是接口(多继承),在已经存在的继承关系的类里面要实现线程只能实现Runnable接口。
  2. Runnable表示一个线程的顶级接口,而Thread类是实现了Runnable接口,所以我们在使用Thread类或者实现Runnable接口都需要实现run()方法。
  3. 站在面向对象的这样一个思想来说,Runnable是一个任务,Thread才是真正处理的线程,所以我们用Runnable定义具体的任务,交给Thread去处理就达到了一共松耦合的一个设计目的。
  4. 接口表示一种规范或者标准,而实现类表示对这个规范或者标准的实现,站在线程的角度,Thread才是真正意义上的线程实现,Runnable表示线程要去执行的一个任务。

5.对守护线程的理解

守护线程跟用户线程创建的方式是一样的。

  1. 在线程创建方面,对于守护线程我们需要主动调用setDaemon()并且设置为true。
  2. 一个Java进程中,只要有任何一个用户进程还在运行,那这个java进程就不会结束,否则这个程序才会终止。

Java进程的终止与否只和用户线程有关,守护线程的生命周期依赖于用户线程,JVM垃圾回收线程就是典型的守护进程。

6.ThreadLocal的底层原理

7.并发、并行、串行之间的区别

  • 串行:每次只能处理一个任务,处理好后才会处理下一个任务。
    • 情景类比:(动车站只开放一个检票口)
          所有人排成一列,按照列的顺序依次从检票口进行检票。
  • 并发:每次只能处理一个任务,但每个任务并不是要处理好才处理下一个而是按照一定的时间轮流执行。
    • 情景类比:(动车站只开放一个检票口)
          所有人排成多列,但由于只开放一个检票口,故多列队伍交替从一个检票口进行检票。
  • 并行:每个处理器都可处理一个任务。
    • 情景类比:(动车站开放多个检票口)
          所有人排成多列,分别从多个检票口进行检票。

8.并发的三大特性

  1. 原子性:确保操作不可分割,即操作要么完全执行,要么完全不执行,不会处于中间状态。在并发环境中,原子性保证了多个线程对共享变量的操作互不干扰。
  2. 可见性:确保一个线程对共享变量的修改能够及时被其他线程看到。在并发环境中,由于每个线程都有自己的工作内存,可能导致数据不一致,因此需要特殊的同步机制(如volatile关键字或锁)来保证可见性。
  3. 有序性:确保程序执行的顺序按照代码的先后顺序执行,防止因编译器优化或硬件的乱序执行导致代码执行顺序发生变化。通过使用锁等机制可以保证代码块的有序执行。

9.线程是什么?多线程是什么

线程:在一个进程中,每个独立的功能都需要独立的去运行,这时又需要把当前这个进程划分成多个区域,每个独立的小区域(小单元)称为一个线程。

例如:360杀毒软件,同时既可以安全体检电脑又可以清理电脑中的垃圾。那么这里的安全体检是360杀毒软件中的一个线程,清理电脑中的垃圾也是一个线程。

多线程:一个进程如果只有一条执行任务,则称为单线程程序。一个进程如果有多条执行任务,也就是说在一个进程中,同时开启多个线程,让多个线程同时去完成某些任务(功能),则称为多线程程序。

10.wait()和sleep()的区别

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

不同点:

  1. wait方法是Object类中的方法,sleep方法是Thread类中的静态方法。
  2. 使用场景:wait方法用于多个线程之间的协作和通信,sleep方法用于线程的休眠。
  3. wait方法必须在synchronized同步块中调用,sleep没有使用限制。
  4. wait方法会释放对象的锁使得当前线程进入等待状态,直到其他线程调用notify方法或notifyall方法唤醒。sleep方法使当前线程暂停执行一段时间,并不会释放对象锁。

11.为什么wait()、notify()、notifyAll()方法定义在Object类里面,而不是Thread类

Java提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。由于wait、notify、notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

12.start()和run()的区别

  • 定义位置不同,run是普通方法,start是启动方法
  • 执行方式不同,run不启动新线程,start启动新线程
  • 线程状态不同,run在当前线程中执行,start在多线程中启动新线程

13.实现多线程的方式

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口
  • 线程池

14.Runnable和Callable的区别

  1. 返回值:runnable接口定义了一个无返回值的run方法,callable接口定义了一个有返回值的call方法。
  2. 异常:runnable接口的run方法不会抛出任何检查型异常,callable接口的call方法会抛出异常。
  3. 使用的角度:Runnable的实例通常与Thread对象一起使用,callable通常与ExecutorService配合使用。

15.线程池的好处

  1. 重复使用存在的线程,减少对象创建、消亡的开销,性能佳。
  2. 可以有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。
  3. 提供定时执行、定期执行、单线程、并发数控制等功能。

16.线程池的七大参数

  • corePoolSize:核心线程数,一旦创建将不会再释放。如果创建的线程数还没有达到指定的核心线程数量,将会继续创建新的核心线程,直到达到最大核心线程数后,核心线程数将不在增加;如果没有空闲的核心线程,同时又未达到最大线程数,则将继续创建非核心线程;如果核心线程数等于最大线程数,则当核心线程数都处于激活状态时,任务将被挂起,等待空闲线程来执行。
  • maximumPoolSize:最大线程数,允许创建的最大线程数量。如果最大线程数等于核心线程数,则无法创建非核心线程;如果非核心线程处于空闲时,超过设置的空闲时间,则将被回收,释放占用的资源。
  • keepAliveTime:对于非核心线程,当线程空闲时保存的最大时间,超过这个时间则被释放销毁。
  • unit:时间单位,TimeUnit.SECONDS等。
  • workQueue:任务队列,存储暂时无法执行的任务,等待空闲线程来执行任务。
  • threadFactory:线程工程,用于创建线程。
  • handler:拒绝策略,当线程边界和队列容量已经达到最大时,用于处理阻塞时的程序。

17.线程池的执行过程

  1. 接到任务,判断核心线程池是否满了,没满执行任务,满了放入等待队列。
  2. 等待队列没满,存入队列,等待执行,满了去查看最大线程数。
  3. 最大线程数没满执行任务,满了执行拒绝策略。

18.线程池的五种创建方式

  1. newCachedThreadPool:是一种可以缓存的线程池,它可以用来处理大量短期的突发流量。
    • 最大线程数是Integer.MaxValue
    • 线程存活时间是60s
    • 阻塞队列用的是Synchronous-Queue
  2. newFixedThreadPool:是一种固定线程数量的线程池。
    • 核心线程和最大线程数量都是一个固定的值。如果任务比较多工作线程处理不过来,就会加入到阻塞队列里面等待。
  3. newSingleThreadExecutor:只有一个工作线程的线程池,并且线程数量无法动态更改,因此可以保证所有的任务都按照FIFO的方式顺序执行。
  4. newScheduledThreadPool:具有延迟执行功能的线程池,可以用它来实现定时调度。
  5. newWorkStealingPool:java8里面新加入的一个线程池,它内部会构建一个ForkJoinPool,利用工作窃取的算法并行处理请求。

19.四大拒绝策略

  1. AbortPolicy():直接抛出异常,阻止系统正常运行(默认策略)。
  2. CallerRunsPolicy:该策略直接将任务交给调用者线程运行性能极有可能会急剧下降。
  3. DiscardOldestPolicy:丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
  4. DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理,如果允许任务丢失,这是最好的一种方案。

以上内置拒绝策略实现了RejectedExecutionHandler接口,若以上策略仍无法满足实际需求,完全可以自己扩展RejectedExecutionHandler接口。

20.shutdown和shutdownNow的区别

  • shutdown没有返回值,shutdownNow会返回没有执行完任务的集合。
  • shutdown不会抛出异常,shutdownNow会抛出异常。
  • shutdown会暂停任务的添加,并等待执行完全线程池的任务在关闭,shutdownNow会给所有线程发中断信号,然后中断任务,关闭线程池。

21.什么是死锁

死锁是一组互相竞争资源的线程因互相等待导致“永久”阻塞的现象。

22.造成死锁的四个必要条件

  1. 互斥条件:共享资源x和y只能被一个线程占用。
  2. 占有且等待:线程t1已经取得共享资源x,在等待共享资源y的时候不释放共享资源x。
  3. 不可抢占:其他线程不能强行抢占线程t1占有的资源。
  4. 循环等待:线程t1等待线程t2占有的资源,线程t2等待线程t1占有的资源。

23.volatile和synchronized的区别

  • volatile仅能使用在变量级别的,synchronized可以使用在变量、方法、类级别的。
  • volatile不具备原子性,具备可见性,synchronized有原子性和可见性。
  • volatile不会造成线程阻塞,synchronized会造成线程阻塞。
  • volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好。

24.synchronized和lock的区别

  1. 从功能角度看,两者都是Java中解决线程安全问题的一个工具。
  2. 从特性来看,synchronized是Java中的同步关键字,lock是JUC包里面提供的一个接口。synchronized通过修饰在方法层面或者代码块去控制锁的粒度。在修饰在代码块中,如果锁对象是类对象或者静态对象就是全局锁,如果是普通实例对象那么这个锁的范围取决于这个实例对象的生命周期。而lock锁的作用域取决于lock实例的生命周期。lock还提供了非阻塞竞争锁的方法trylock(),提供了公平锁和非公平锁。synchronized只有非公平锁。
  3. 从性能来看,synchronized使用了偏向锁、轻量级锁、重量级锁以及锁升级的机制去实现锁的优化。lock采用了自旋锁的方式去实现锁的优化。

25.JMM(java内存模型)

JMM是Java内存模型(Java Memory Model),简称JMM。它本身只是一个抽象的概念,并不是真实存在,它描述的是一种规则或规范,是和多线程相关的一组规范。它定义了线程如何与内存交互,如何保证多线程程序的正确性。

26.JMM的约定

  1. 线程解锁前,必须把共享变量立即刷回主存。
  2. 线程加锁前,必须读取主存中的最新值到工作内存中。
  3. 加锁和解锁必须是同一把锁。

27.JMM的八个命令

为了支持JMM,定义了8条原子操作,用于主存和工作内存的交互。

  • lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
  • unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用。
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量。
  • store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中。

28.为什么要有JMM,用来解决什么问题

解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

该叫啥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值