2020 Java面试题最新(四线程篇)

主要写面试中问到的线程的几个重要的方法,实现的原理,如何创建,线程池的原理以及线程生命周期等

1.谈谈创建线程的方式及实现

继承 Thread 类创建线程类

  • 定义 Thread 类的子类,并重写该类的 run 方法,该 run 方法的方法体就代表了线程要完成的任务。因此把 run() 方法称为执行体
  • 创建 Thread 子类的实例,即创建了线程对象
  • 调用线程对象的 start() 方法来启动该线程

通过 Runnable 接口创建线程类

  • 定义 Runnable 接口的实现类,并重写该接口的 run() 方法,该 run() 方法的方法体同样是该线程的线程执行体
  • 创建 Runnable 实现类的实例,并依此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象
  • 调用线程对象的 start() 方法来启动该线程

注意 许多人都认为只有这两种方式,其实不然,还有一种方式

通过 Callable 和 Future 创建线程

  • 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值
  • 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值
  • 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程
  • 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值

另外补充说明 采用实现 Runnable、Callable 接口的方式创见多线程时:

  • 优势是:线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想
  • 劣势是:编程稍微复杂,如果要访问当前线程,则必须使用 Thread.currentThread() 方法

使用继承 Thread 类的方式创建多线程时:

  • 优势是:编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程
  • 劣势是:线程类已经继承了 Thread 类,所以不能再继承其他父类

说说sleep()、join()、yield()有什么区别

sleep()

sleep() 方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是 sleep() 方法不会释放“锁标志”,也就是说如果有 synchronized 同步块,其他线程仍然不能访问共享数据

wait()

wait() 方法需要和 notify() 及 notifyAll() 两个方法一起介绍,这三个方法用于协调多个线程对共享数据的存取,所以必须在 synchronized 语句块内使用,也就是说,调用 wait(),notify() 和 notifyAll() 的任务在调用这些方法前必须拥有对象的锁。注意,它们都是 Object 类的方法,而不是 Thread 类的方法。

wait() 方法与 sleep() 方法的不同之处在于,wait() 方法会释放对象的“锁标志”。当调用某一对象的 wait() 方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了 notify() 方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的 notifyAll() 方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。

除了使用 notify() 和 notifyAll() 方法,还可以使用带毫秒参数的 wait(long timeout) 方法,效果是在延迟 timeout 毫秒后,被暂停的线程将被恢复到锁标志等待池。

此外,wait(),notify() 及 notifyAll() 只能在 synchronized 语句中使用,但是如果使用的是 ReenTrantLock 实现同步,该如何达到这三个方法的效果呢?解决方法是使用 ReenTrantLock.newCondition() 获取一个 Condition 类对象,然后 Condition 的 await(),signal() 以及 signalAll() 分别对应上面的三个方法

yield()

yield() 方法和 sleep() 方法类似,也不会释放“锁标志”,区别在于,它没有参数,即 yield() 方法只是使当前线程重新回到可执行状态,所以执行 yield() 的线程有可能在进入到可执行状态后马上又被执行,另外 yield() 方法只能使同优先级或者高优先级的线程得到执行机会,这也和 sleep() 方法不同

join()

join() 方法会使当前线程等待调用 join() 方法的线程结束后才能继续执行

3.说说 CountDownLatch 原理

CountDownLatch 内部维护了一个整数 n,n(要大于等于0)在 当前线程 初始化 CountDownLatch 方法指定。当前线程调用 CountDownLatch 的 await() 方法阻塞当前线程,等待其他调用 CountDownLatch 对象的 CountDown() 方法的线程执行完毕。 其他线程调用该 CountDownLatch 的 CountDown() 方法,该方法会把 n-1,直到所有线程执行完成,n 等于 0,当前线程 就恢复执行

4.说说 CyclicBarrier 原理

CyclicBarrier 是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点(CommonBarrierPoint)。因为该 barrier 在释放等待线程后可以重用,所以称它为循环的 barrier

5.说说 Semaphore 原理

Semaphore 直译为信号。实际上 Semaphore 可以看做是一个信号的集合。不同的线程能够从 Semaphore 中获取若干个信号量。当 Semaphore 对象持有的信号量不足时,尝试从 Semaphore 中获取信号的线程将会阻塞。直到其他线程将信号量释放以后,阻塞的线程会被唤醒,重新尝试获取信号量

6.说说 Exchanger 原理

当一个线程到达 exchange 调用点时,如果它的伙伴线程此前已经调用了此方法,那么它的伙伴会被调度唤醒并与之进行对象交换,然后各自返回。如果它的伙伴还没到达交换点,那么当前线程将会被挂起,直至伙伴线程到达——完成交换正常返回;或者当前线程被中断——抛出中断异常;又或者是等候超时——抛出超时异常

7.说说 CountDownLatch 与 CyclicBarrier 区别

  1. CountDownLatch 的作用是允许 1 或 N 个线程等待其他线程完成执行;而 CyclicBarrier 则是允许 N 个线程相互等待
  2. CountDownLatch 的计数器无法被重置; CyclicBarrier 的计数器可以被重置后使用,因此它被称为是循环的 barrier

8.ThreadLocal 原理分析

ThreadLocal 提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同。ThreadLocal 相当于提供了一种线程隔离,将变量与线程相绑定

9.讲讲线程池的实现原理

当提交一个新任务到线程池时,线程池的处理流程如下:

  1. 线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程
  2. 线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程
  3. 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务

10.线程池的几种方式与使用场景

在 Executors 类里面提供了一些静态工厂,生成一些常用的线程池

  1. newFixedThreadPool:创建固定大小的线程池。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
  2. newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小
  3. newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行
  4. newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求
  5. newSingleThreadScheduledExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求

11.线程的生命周期

新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态

希望这一章节的内容对大家来说有帮助 大家看到感觉有用的请继续观看这个系列

前几期系列有心态篇,基础篇,集合篇

本系列总共所涉及Java基础,集合,线程,锁机制,spring框架,分布式,微服务,数据存储,缓存使用,消息队列,安全,性能调优,设计模式以及需求分析

发布了5 篇原创文章 · 获赞 9 · 访问量 933

猜你喜欢

转载自blog.csdn.net/weixin_43291173/article/details/104238188
今日推荐