当前位置:   article > 正文

Java面试高频题汇总:线程相关面试及答案

Java面试高频题汇总:线程相关面试及答案

说明在前:

在文档中对所有的面试题都进行了难易程度和出现频率的等级说明

星数越多代表权重越大,最多五颗星(☆☆☆☆☆) 最少一颗星(☆)

线程的基础知识

1. 线程和进程的区别?

难易程度:☆☆
出现频率:☆☆☆

程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的。

当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。

一个进程之内可以分为一到多个线程。

一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行

Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。在 windows 中进程是不活动的,只是作为线程的容器

二者对比

  • 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务
  • 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)

2. 并行和并发有什么区别?

难易程度:☆
出现频率:☆

单核CPU

  • 单核CPU下线程实际还是串行执行的
  • 操作系统中有一个组件叫做任务调度器,将cpu的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于cpu在线程间(时间片很短)的切换非常快,人类感觉是同时运行的 。
  • 总结为一句话就是: 微观串行,宏观并行

一般会将这种线程轮流使用CPU的做法称为并发(concurrent)

多核CPU

每个核(core)都可以调度运行线程,这时候线程可以是并行的。

并发(concurrent)是同一时间应对(dealing with)多件事情的能力

并行(parallel)是同一时间动手做(doing)多件事情的能力

举例:
  • 家庭主妇做饭、打扫卫生、给孩子喂奶,她一个人轮流交替做这多件事,这时就是并发
  • 家庭主妇雇了个保姆,她们一起这些事,这时既有并发,也有并行(这时会产生竞争,例如锅只有一口,一个人用锅时,另一个人就得等待)
  • 雇了3个保姆,一个专做饭、一个专打扫卫生、一个专喂奶,互不干扰,这时是并行

3. 创建线程的四种方式

难易程度:☆☆
出现频率:☆☆☆☆

参考回答:

共有四种方式可以创建线程,分别是:继承Thread类、实现runnable接口、实现Callable接口、线程池创建线程

详细创建方式参考下面代码:

① 继承Thread类

  1. public class MyThread extends Thread {
  2. @Override
  3. public void run() {
  4. System.out.println("MyThread...run...");
  5. }
  6. public static void main(String[] args) {
  7. // 创建MyThread对象
  8. MyThread t1 = new MyThread() ;
  9. MyThread t2 = new MyThread() ;
  10. // 调用start方法启动线程
  11. t1.start();
  12. t2.start();
  13. }
  14. }

② 实现runnable接口

  1. public class MyRunnable implements Runnable{
  2. @Override
  3. public void run() {
  4. System.out.println("MyRunnable...run...");
  5. }
  6. public static void main(String[] args) {
  7. // 创建MyRunnable对象
  8. MyRunnable mr = new MyRunnable() ;
  9. // 创建Thread对象
  10. Thread t1 = new Thread(mr) ;
  11. Thread t2 = new Thread(mr) ;
  12. // 调用start方法启动线程
  13. t1.start();
  14. t2.start();
  15. }
  16. }

③ 实现Callable接口

  1. public class MyCallable implements Callable<String> {
  2. @Override
  3. public String call() throws Exception {
  4. System.out.println("MyCallable...call...");
  5. return "OK";
  6. }
  7. public static void main(String[] args) throws ExecutionException, InterruptedException {
  8. // 创建MyCallable对象
  9. MyCallable mc = new MyCallable() ;
  10. // 创建F
  11. FutureTask<String> ft = new FutureTask<String>(mc) ;
  12. // 创建Thread对象
  13. Thread t1 = new Thread(ft) ;
  14. Thread t2 = new Thread(ft) ;
  15. // 调用start方法启动线程
  16. t1.start();
  17. // 调用ft的get方法获取执行结果
  18. String result = ft.get();
  19. // 输出
  20. System.out.println(result);
  21. }
  22. }

④ 线程池创建线程

  1. public class MyExecutors implements Runnable{
  2. @Override
  3. public void run() {
  4. System.out.println("MyRunnable...run...");
  5. }
  6. public static void main(String[] args) {
  7. // 创建线程池对象
  8. ExecutorService threadPool = Executors.newFixedThreadPool(3);
  9. threadPool.submit(new MyExecutors()) ;
  10. // 关闭线程池
  11. threadPool.shutdown();
  12. }
  13. }

4. runnable 和 callable 有什么区别

难易程度:☆☆
出现频率:☆☆☆

参考回答:

  1. Runnable 接口run方法没有返回值;Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
  2. Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
  3. Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛

5. 线程的 run()和 start()有什么区别?

难易程度:☆☆
出现频率:☆☆

start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。

run(): 封装了要被线程执行的代码,可以被调用多次。

6. 线程包括哪些状态,状态之间是如何变化的

难易程度:☆☆☆
出现频率:☆☆☆☆

线程的状态可以参考JDK中的Thread类中的枚举State

  1. public enum State {
  2. /**
  3. * 尚未启动的线程的线程状态
  4. */
  5. NEW,
  6. /**
  7. * 可运行线程的线程状态。处于可运行状态的线程正在 Java 虚拟机中执行,但它可能正在等待来自 * 操作系统的其他资源,例如处理器。
  8. */
  9. RUNNABLE,
  10. /**
  11. * 线程阻塞等待监视器锁的线程状态。处于阻塞状态的线程正在等待监视器锁进入同步块/方法或在调 *Object.wait后重新进入同步块/方法。
  12. */
  13. BLOCKED,
  14. /**
  15. * 等待线程的线程状态。由于调用以下方法之一,线程处于等待状态:
  16. * Object.wait没有超时
  17. * 没有超时的Thread.join
  18. * LockSupport.park
  19. * 处于等待状态的线程正在等待另一个线程执行特定操作。
  20. * 例如,一个对对象调用Object.wait()的线程正在等待另一个线程对该对象调用Object.notify() *Object.notifyAll() 。已调用Thread.join()的线程正在等待指定线程终止。
  21. */
  22. WAITING,
  23. /**
  24. * 具有指定等待时间的等待线程的线程状态。由于以指定的正等待时间调用以下方法之一,线程处于定 * 时等待状态:
  25. * Thread.sleep
  26. * Object.wait超时
  27. * Thread.join超时
  28. * LockSupport.parkNanos
  29. * LockSupport.parkUntil
  30. * </ul>
  31. */
  32. TIMED_WAITING,
  33. /**
  34. * 已终止线程的线程状态。线程已完成执行
  35. */
  36. TERMINATED;
  37. }

状态之间是如何变化的

分别是

  • 新建
  • 当一个线程对象被创建,但还未调用 start 方法时处于新建状态
  • 此时未与操作系统底层线程关联
  • 可运行
  • 调用了 start 方法,就会由新建进入可运行
  • 此时与底层线程关联,由操作系统调度执行
  • 终结
  • 线程内代码已经执行完毕,由可运行进入终结
  • 此时会取消与底层线程关联
  • 阻塞
  • 当获取锁失败后,由可运行进入 Monitor 的阻塞队列阻塞,此时不占用 cpu 时间
  • 当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行状态
  • 等待
  • 当获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从可运行状态释放锁进入 Monitor 等待集合等待,同样不占用 cpu 时间
  • 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的等待线程,恢复为可运行状态
  • 有时限等待
  • 当获取锁成功后,但由于条件不满足,调用了 wait(long) 方法,此时从可运行状态释放锁进入 Monitor 等待集合进行有时限等待,同样不占用 cpu 时间
  • 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的有时限等待线程,恢复为可运行状态,并重新去竞争锁
  • 如果等待超时,也会从有时限等待状态恢复为可运行状态,并重新去竞争锁
  • 还有一种情况是调用 sleep(long) 方法也会从可运行状态进入有时限等待状态,但与 Monitor 无关,不需要主动唤醒,超时时间到自然恢复为可运行状态

7. 新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?

难易程度:☆☆
出现频率:☆☆☆

在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。

代码举例:

为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成

  1. public class JoinTest {
  2. public static void main(String[] args) {
  3. // 创建线程对象
  4. Thread t1 = new Thread(() -> {
  5. System.out.println("t1");
  6. }) ;
  7. Thread t2 = new Thread(() -> {
  8. try {
  9. t1.join(); // 加入线程t1,只有t1线程执行完毕以后,再次执行该线程
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println("t2");
  14. }) ;
  15. Thread t3 = new Thread(() -> {
  16. try {
  17. t2.join(); // 加入线程t2,只有t2线程执行完毕以后,再次执行该线程
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. System.out.println("t3");
  22. }) ;
  23. // 启动线程
  24. t1.start();
  25. t2.start();
  26. t3.start();
  27. }
  28. }

8. notify()和 notifyAll()有什么区别?

难易程度:☆☆
出现频率:☆☆

notifyAll:唤醒所有wait的线程

notify:只随机唤醒一个 wait 线程

  1. package com.itheima.basic;
  2. public class WaitNotify {
  3. static boolean flag = false;
  4. static Object lock = new Object();
  5. public static void main(String[] args) {
  6. Thread t1 = new Thread(() -> {
  7. synchronized (lock){
  8. while (!flag){
  9. System.out.println(Thread.currentThread().getName()+"...wating...");
  10. try {
  11. lock.wait();
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. }
  16. System.out.println(Thread.currentThread().getName()+"...flag is true");
  17. }
  18. });
  19. Thread t2 = new Thread(() -> {
  20. synchronized (lock){
  21. while (!flag){
  22. System.out.println(Thread.currentThread().getName()+"...wating...");
  23. try {
  24. lock.wait();
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. System.out.println(Thread.currentThread().getName()+"...flag is true");
  30. }
  31. });
  32. Thread t3 = new Thread(() -> {
  33. synchronized (lock) {
  34. System.out.println(Thread.currentThread().getName() + " hold lock");
  35. lock.notifyAll();
  36. flag = true;
  37. try {
  38. Thread.sleep(2000);
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. }
  42. }
  43. });
  44. t1.start();
  45. t2.start();
  46. t3.start();
  47. }
  48. }

9. 在 java 中 wait 和 sleep 方法的不同?

难易程度:☆☆☆
出现频率:☆☆☆

参考回答:

共同点

  • wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态

不同点

  • 方法归属不同
  • sleep(long) 是 Thread 的静态方法
  • 而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有
  • 醒来时机不同
  • 执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来
  • wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去
  • 它们都可以被打断唤醒
  • 锁特性不同(重点)
  • wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制
  • wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)
  • 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)

代码示例:

  1. public class WaitSleepCase {
  2. static final Object LOCK = new Object();
  3. public static void main(String[] args) throws InterruptedException {
  4. sleeping();
  5. }
  6. private static void illegalWait() throws InterruptedException {
  7. LOCK.wait();
  8. }
  9. private static void waiting() throws InterruptedException {
  10. Thread t1 = new Thread(() -> {
  11. synchronized (LOCK) {
  12. try {
  13. get("t").debug("waiting...");
  14. LOCK.wait(5000L);
  15. } catch (InterruptedException e) {
  16. get("t").debug("interrupted...");
  17. e.printStackTrace();
  18. }
  19. }
  20. }, "t1");
  21. t1.start();
  22. Thread.sleep(100);
  23. synchronized (LOCK) {
  24. main.debug("other...");
  25. }
  26. }
  27. private static void sleeping() throws InterruptedException {
  28. Thread t1 = new Thread(() -> {
  29. synchronized (LOCK) {
  30. try {
  31. get("t").debug("sleeping...");
  32. Thread.sleep(5000L);
  33. } catch (InterruptedException e) {
  34. get("t").debug("interrupted...");
  35. e.printStackTrace();
  36. }
  37. }
  38. }, "t1");
  39. t1.start();
  40. Thread.sleep(100);
  41. synchronized (LOCK) {
  42. main.debug("other...");
  43. }
  44. }
  45. }

10. 如何停止一个正在运行的线程?

难易程度:☆☆
出现频率:☆☆

参考回答:

有三种方式可以停止线程

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
  • 使用stop方法强行终止(不推荐,方法已作废)
  • 使用interrupt方法中断线程

代码参考如下:

① 使用退出标志,使线程正常退出

  1. public class MyInterrupt1 extends Thread {
  2. volatile boolean flag = false ; // 线程执行的退出标记
  3. @Override
  4. public void run() {
  5. while(!flag) {
  6. System.out.println("MyThread...run...");
  7. try {
  8. Thread.sleep(3000);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. }
  14. public static void main(String[] args) throws InterruptedException {
  15. // 创建MyThread对象
  16. MyInterrupt1 t1 = new MyInterrupt1() ;
  17. t1.start();
  18. // 主线程休眠6
  19. Thread.sleep(6000);
  20. // 更改标记为true
  21. t1.flag = true ;
  22. }
  23. }

② 使用stop方法强行终止

  1. public class MyInterrupt2 extends Thread {
  2. volatile boolean flag = false ; // 线程执行的退出标记
  3. @Override
  4. public void run() {
  5. while(!flag) {
  6. System.out.println("MyThread...run...");
  7. try {
  8. Thread.sleep(3000);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. }
  14. public static void main(String[] args) throws InterruptedException {
  15. // 创建MyThread对象
  16. MyInterrupt2 t1 = new MyInterrupt2() ;
  17. t1.start();
  18. // 主线程休眠2
  19. Thread.sleep(6000);
  20. // 调用stop方法
  21. t1.stop();
  22. }
  23. }

③ 使用interrupt方法中断线程

  1. package com.itheima.basic;
  2. public class MyInterrupt3 {
  3. public static void main(String[] args) throws InterruptedException {
  4. //1.打断阻塞的线程
  5. /*Thread t1 = new Thread(()->{
  6. System.out.println("t1 正在运行...");
  7. try {
  8. Thread.sleep(5000);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }, "t1");
  13. t1.start();
  14. Thread.sleep(500);
  15. t1.interrupt();
  16. System.out.println(t1.isInterrupted());*/
  17. //2.打断正常的线程
  18. Thread t2 = new Thread(()->{
  19. while(true) {
  20. Thread current = Thread.currentThread();
  21. boolean interrupted = current.isInterrupted();
  22. if(interrupted) {
  23. System.out.println("打断状态:"+interrupted);
  24. break;
  25. }
  26. }
  27. }, "t2");
  28. t2.start();
  29. Thread.sleep(500);
  30. // t2.interrupt();
  31. }
  32. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/111564
推荐阅读
相关标签
  

闽ICP备14008679号