多线程相关面试题

简介: 简介: 哈喽,大家好~我是你们的老朋友:保护小周ღ,本期为大家带来的是多线程相关面试题, 简述 synchronized 和 ReentrantLock 的区别,wait() 和 sleep() 的区别volatile 关键字的作用,线程池的执行流程和拒绝策略,Callable 带返回值的任务更多精彩敬请期待:保护小周ღ *★,°*:.☆( ̄▽ ̄)/$:*.°★* ‘

一、简述 synchronized 和 ReentrantLock 的区别

  1. 使用方法不同

Synchronized 可以用来修饰普通方法,静态代码块和普通代码块

ReentrantLock 只能在普通代码块上使用

  1. 获取锁和释放锁的方式不同

Synchronized 会自动的加锁和释放锁,当进入synchronized 修饰的代码块之后会自动的加锁,当离开代码块的时候会自动的释放锁

ReentrantLock 需要手动的加锁和释放锁

lock() 方法加锁, unlock() 方法释放锁

unlock() 释放锁一定要放在 finally中,否则有可能会出现锁一直被占用,从而导致其他线程一直阻塞等待的

  1. Synchronized 是非公平锁,而 ReentrantLock 既可以是公平锁,也可以是非公平锁,默认是非公平锁,也可以手动指定为公平锁
  2. 中断响应不同

ReentrantLock 可以使用 lockInterruptibly 获取锁并响应中断指令,而 synchronized 不能响应中断,如果发生了死锁就会一直等待下去,而使用 ReentrantLock 可以响应中断并释放锁,从而解决死锁的问题。

  1. 底层实现不同

synchronized 是 JVM 层面通过监视器实现的,而 ReentrantLock 是基于 AQS 实现的

AQS,全称是 AbstractQueuedSynchronizer,中文译为抽象队列式同步器。这个抽象类对于JUC并发包非常重要,JUC包中的ReentrantLock,,Semaphore,ReentrantReadWriteLock,CountDownLatch 等等几乎所有的类都是基于AQS实现的。


二、volatile 关键字的作用

  1. volatile 是java 中的关键字,是一个变量修饰符,常常被用来修饰需要被不同线程访问和修改的变量,
  2. 被 volatile 修饰的变量具有可见性,当一个线程修改了该变量的值时,其他线程如果对该变量进行操作的时候会从内存中重新读取该变量的数据,可以保障了数据的有效性,避免内存可见性造成的线程安全问题(bug)
  3. volatile 只能保证单次读/写操作的原子性(针对一条指令),不能保证多步操作的原子性。例如:修改一个变量的值:第一步将数据从内存中读取到寄存器中,cpu 从寄存器中读取数据,对数据进行处理后重新写入到内存,我们可以把三步操作看作是修改变量的一个事件,使用 volatile 不能保证该事件的原子性,可能执行第一步的时候cpu 就执行了别的线程,如果别的线程也对同一变量进行修改,就会造成bug , 针对这个问题我们需要使用 synchrnized 对事件进行加锁,即可保证事件的原子性,此时其他线程如果也需要对同一变量进行操作,只能阻塞等待当前线程将事件处理完毕。
  4. 在 Java 内存模型中,允许编译器和处理器对指令进行重排序(最优的处理效率),重排序过程不会影响到单线程程序的执行结果,但是可能会影响到多线程并发执行的正确性。volatile 修饰的变量的读写指令不能和其前后的任何指令重排序,其前后的指令可能会被重排序, volatile 关键字可以禁止指令重排序,

三、wait()  和 sleep() 的区别

  1. wait是Object类中的一个方法,sleep是Thread类中的一个方法;
  2. wait必须在synchronized修饰的代码块或方法中使用,sleep方法可以在任何位置使用;
  3. wait被调用后当前线程进入BLOCK状态并释放当前对象锁,并可以通过notify和notifyAll方法进行唤醒;sleep被调用后当前线程进入TIMED_WAIT状态,不涉及锁相关的操作;

四、线程池的执行流程和拒绝策略

线程池的执行流程有 3 个重要的判断点:

判断当前工作的线程数是否小于核心线程数

判断当前任务队列是否已满.

判断当前线程数是否已达最大线程数.

如果在经过上述三个过程后, 得到的结果都是 true , 那么就会执行线程池的拒绝策略.


拒绝策略有四种,是封装在 ThreadPoolExecutor类中的静态方法

AbortPolicy:中止策略,线程池会抛出RejectedRxecutionException异常并中止执行此任务.

CallerRunsPolicy:把任务交给添加此任务的(main)线程来执行.

DiscardPolicy:忽略此任务,忽略最新的一个任务.

DiscardOldestPolicy:忽略最早的任务,最先加入队列的任务


五、Callable 带返回值的任务

创建一个匿名内部类,实现 Callable接口. Callable带有泛型参数.泛型参数表示返回值的类型.

重写 Callable的 call()方法,完成累加的过程.直接通过返回值返回计算结果.

把 callable实例使用 FutureTask包装一下.

创建线程,线程的构造方法传入 FutureTask .此时新线程就会执行 FutureTask内部的

Callable的call方法,完成计算.计算结果就放到了 FutureTask对象中.

在主线程中调用 futureTask.get()能够阻塞等待新线程计算完毕.并获取到 FutureTask中的结果.

可以看到,使用 Callable和 FutureTask之后,代码简化了很多,也不必手动写线程同步代码了.

Callable<Integer>callable=newCallable<Integer>() {
@OverridepublicIntegercall() throwsException {
intsum=0;
for (inti=1; i<=1000; i++) {
sum+=i;
      }
returnsum;
  }
};
FutureTask<Integer>futureTask=newFutureTask<>(callable);
Threadt=newThread(futureTask);
t.start();
intresult=futureTask.get();
System.out.println(result);

Callable和 Runnable相对,都是描述一个 “任务”. Callable描述的是带有返回值的任务,Runnable描述的是不带返回值的任务.Callable通常需要搭配 FutureTask来使用. FutureTask用来保存 Callable的返回结果.因为Callable往往是在另一个线程中执行的,啥时候执行完并不确定.FutureTask就可以负责这个等待结果出来的工作.

理解 FutureTask

想象去吃麻辣烫.当餐点好后,后厨就开始做了.同时前台会给你一张 “小票” .这个小票就是FutureTask.后面我们可以随时凭这张小票去查看自己的这份麻辣烫做出来了没.

相关面试题

介绍下 Callable是什么

Callable是一个 interface .相当于把线程封装了一个 “返回值”.方便程序猿借助多线程的方式计算结果.

Callable和 Runnable相对,都是描述一个 “任务”. Callable描述的是带有返回值的任务,Runnable描述的是不带返回值的任务.

Callable通常需要搭配 FutureTask来使用. FutureTask用来保存 Callable的返回结果.因为Callable往往是在另一个线程中执行的,啥时候执行完并不确定.



相关文章
|
4天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
45 14
|
8天前
|
数据采集 Java Linux
面试大神教你:如何巧妙回答线程优先级这个经典考题?
大家好,我是小米。本文通过故事讲解Java面试中常见的线程优先级问题。小明和小华的故事帮助理解线程优先级:高优先级线程更可能被调度执行,但并非越高越好。实际开发需权衡业务需求,合理设置优先级。掌握线程优先级不仅能写出高效代码,还能在面试中脱颖而出。最后,小张因深入分析成功拿下Offer。希望这篇文章能助你在面试中游刃有余!
30 4
面试大神教你:如何巧妙回答线程优先级这个经典考题?
|
11天前
|
缓存 安全 Java
面试中的难题:线程异步执行后如何共享数据?
本文通过一个面试故事,详细讲解了Java中线程内部开启异步操作后如何安全地共享数据。介绍了异步操作的基本概念及常见实现方式(如CompletableFuture、ExecutorService),并重点探讨了volatile关键字、CountDownLatch和CompletableFuture等工具在线程间数据共享中的应用,帮助读者理解线程安全和内存可见性问题。通过这些方法,可以有效解决多线程环境下的数据共享挑战,提升编程效率和代码健壮性。
43 6
|
27天前
|
算法 安全 Java
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
68 16
|
1月前
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
1月前
|
Java Linux 调度
硬核揭秘:线程与进程的底层原理,面试高分必备!
嘿,大家好!我是小米,29岁的技术爱好者。今天来聊聊线程和进程的区别。进程是操作系统中运行的程序实例,有独立内存空间;线程是进程内的最小执行单元,共享内存。创建进程开销大但更安全,线程轻量高效但易引发数据竞争。面试时可强调:进程是资源分配单位,线程是CPU调度单位。根据不同场景选择合适的并发模型,如高并发用线程池。希望这篇文章能帮你更好地理解并回答面试中的相关问题,祝你早日拿下心仪的offer!
39 6
|
1月前
|
安全 Java 程序员
面试直击:并发编程三要素+线程安全全攻略!
并发编程三要素为原子性、可见性和有序性,确保多线程操作的一致性和安全性。Java 中通过 `synchronized`、`Lock`、`volatile`、原子类和线程安全集合等机制保障线程安全。掌握这些概念和工具,能有效解决并发问题,编写高效稳定的多线程程序。
66 11
|
1月前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
2月前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
2月前
|
Java 调度