多线程相关面试题

简介: 简介: 哈喽,大家好~我是你们的老朋友:保护小周ღ,本期为大家带来的是多线程相关面试题, 简述 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往往是在另一个线程中执行的,啥时候执行完并不确定.



相关文章
|
2月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
2月前
|
消息中间件 前端开发 NoSQL
面试官:线程池遇到未处理的异常会崩溃吗?
面试官:线程池遇到未处理的异常会崩溃吗?
73 3
面试官:线程池遇到未处理的异常会崩溃吗?
|
2月前
|
消息中间件 前端开发 NoSQL
面试官:如何实现线程池任务编排?
面试官:如何实现线程池任务编排?
33 1
面试官:如何实现线程池任务编排?
|
3月前
|
Java
【多线程面试题二十五】、说说你对AQS的理解
这篇文章阐述了对Java中的AbstractQueuedSynchronizer(AQS)的理解,AQS是一个用于构建锁和其他同步组件的框架,它通过维护同步状态和FIFO等待队列,以及线程的阻塞与唤醒机制,来实现同步器的高效管理,并且可以通过实现特定的方法来自定义同步组件的行为。
【多线程面试题二十五】、说说你对AQS的理解
|
3月前
|
消息中间件 缓存 算法
Java多线程面试题总结(上)
进程和线程是操作系统管理程序执行的基本单位,二者有明显区别: 1. **定义与基本单位**:进程是资源分配的基本单位,拥有独立的内存空间;线程是调度和执行的基本单位,共享所属进程的资源。 2. **独立性与资源共享**:进程间相互独立,通信需显式机制;线程共享进程资源,通信更直接快捷。 3. **管理与调度**:进程管理复杂,线程管理更灵活。 4. **并发与并行**:进程并发执行,提高资源利用率;线程不仅并发还能并行执行,提升执行效率。 5. **健壮性**:进程更健壮,一个进程崩溃不影响其他进程;线程崩溃可能导致整个进程崩溃。
47 2
|
3月前
|
存储 安全 容器
【多线程面试题二十一】、 分段锁是怎么实现的?
这篇文章解释了分段锁的概念和实现方式,通过将数据分成多个段并在每段数据上使用独立锁,从而降低锁竞争,提高并发访问效率,举例说明了`ConcurrentHashMap`如何使用分段锁技术来实现高并发和线程安全。
【多线程面试题二十一】、 分段锁是怎么实现的?
|
3月前
|
存储 缓存 安全
Java多线程面试题总结(中)
Java内存模型(JMM)定义了程序中所有变量的访问规则与范围,确保多线程环境下的数据一致性。JMM包含主内存与工作内存的概念,通过8种操作管理两者间的交互,确保原子性、可见性和有序性。`synchronized`和`volatile`关键字提供同步机制,前者确保互斥访问,后者保证变量更新的可见性。多线程操作涉及不同状态,如新建(NEW)、可运行(RUNNABLE)等,并可通过中断、等待和通知等机制协调线程活动。`volatile`虽不确保线程安全,但能确保变量更新对所有线程可见。
19 0
|
3月前
|
Java 程序员 容器
【多线程面试题二十四】、 说说你对JUC的了解
这篇文章介绍了Java并发包java.util.concurrent(简称JUC),它是JSR 166规范的实现,提供了并发编程所需的基础组件,包括原子更新类、锁与条件变量、线程池、阻塞队列、并发容器和同步器等多种工具。
|
3月前
|
缓存 Java
【多线程面试题二十三】、 说说你对读写锁的了解volatile关键字有什么用?
这篇文章讨论了Java中的`volatile`关键字,解释了它如何保证变量的可见性和禁止指令重排,以及它不能保证复合操作的原子性。
|
3月前
|
Java
【多线程面试题二十二】、 说说你对读写锁的了解
这篇文章讨论了读写锁(ReadWriteLock)的概念和应用场景,强调了读写锁适用于读操作远多于写操作的情况,并介绍了Java中`ReentrantReadWriteLock`实现的读写锁特性,包括公平性选择、可重入和可降级。

热门文章

最新文章

下一篇
无影云桌面