了解这两个接口后,阿里多线程面试题秒AC

简介: 大家好,我是指北君我们知道,阿里面试时非常喜欢考Java多线程编程题,如果你AC不了,可能会给面试官留下一个基础不扎实的印象,影响到你offer的收割,想当年指北君面阿里时就因为秒AC了一道多线程面试题,让面试官刮目相看,所以我们需要重视Java多线程编程。

一般在解决多线程编程题时,我们都离不开JUC并发包下的各种工具类,特别是ReentrantLock锁,它能提供互斥与线程同步的能力,那它是如何获得这个能力的呢?今天指北君就来详细说说给它提供强大能力的两大接口。(PS:文末有当年指北君面试阿里的多线程编程原题以及答案喔)

我们知道,并发领域中有两大核心问题:互斥与同步问题,Java在1.5版本之前,是提供了synchronized来实现的。synchronized是内置锁,虽然在大部分情况下它都能很好的工作,但是依然还是会存在一些局限性,除了当时1.5版本的性能问题外(1.6版本后,synchronized的性能已经得到了很大的优化),还有如下两个问题:

  1. 无法解决死锁问题
  2. 最多使用一个条件变量

所以针对这些问题,Doug Lea在并发包中增加了两个接口Lock和Condition来解决这两个问题,所以指北君今天就说说这两个接口是如何解决synchronized中的这两个问题的。


一. Lock接口


1.1 介绍


在我们分析Lock接口是如何解决死锁问题之前,我们先看看死锁是如何产生的。死锁的产生需要满足下面四个条件:

  1. 互斥:共享资源同一时间只能被一个线程占用
  2. 不可抢占:其他线程不能强行占有另一个线程的资源
  3. 占有且等待:线程在等待其他资源时,不释放自己已占有的资源
  4. 循环等待:线程1和线程2互相占有对方的资源并相互等待

所以,我们只需要破坏上面条件中的任意一个,即可打破死锁。但需要注意的是,互斥条件是不能破坏的,因为使用锁的目的就是为了互斥。所以Lock接口通过破坏掉 "不可抢占"这个条件来解决死锁,具体如下:

  1. 非阻塞获取锁:尝试获取锁,如果失败了就立刻返回失败,这样就可以释放已经持有的其他锁
  2. 响应中断:如果发生死锁后,此线程被其他线程中断,则会释放锁,解除死锁
  3. 支持超时:一段时间内获取不到锁,就返回失败,这样就可以释放之前已经持有的锁

接下来我们具体看看接口代码吧。


1.2 源码解读

30.png

31.png

还需要额外注意的一点,使用synchronized作为锁时,我们是不需要考虑释放锁的,但Lock是属于显示锁,是需要我们手动释放锁的。我们一般在finally块中调用lock.unlock()手动释放锁,具体形式如下:

32.png我们最后通过一张图来总结下Lock接口:


33.jpg


二. Condition接口


2.1 介绍

针对synchronized最多只能使用一个条件变量的问题,Condition接口提供了解决方案。但是为什么多个条件变量就比一个条件变量好呢?我们先来看看synchronized使用一个条件变量时会有什么弊端。

一个synchronized内置锁只对应一个等待容器(wait set),当线程调用wait方法时,会把当前线程放入到同一个等待容器中,当我们需要根据某些特定的条件来唤醒符合条件的线程时,我们只能先从等待容器里唤醒一个线程后,再看是否符合条件。如果不符合条件,则需要将此线程继续wait,然后再去等待容器中获取下一个线程再判断是否满足条件。这样会导致许多无意义的cpu开销。

我们可以看到Lock接口中有个newCondition()的方法:

34.png

通过这个方法,一个锁可以建立多个Conditiion,每个Condtition都有一个容器来保存相应的等待线程,拿到锁的线程根据特定的条件唤醒对应的线程时,只需要去唤醒对应的Contition内置容器中的线程即可,这样就可以减少无意义的CPU开销。然后我们具体看看Condition接口的源码。

2.2 源码解读

100.png36.png


需要注意的是,Object类的等待方法是没有返回值的,但Condtition类中的部分等待方法是有返回值的。awaitNanos(long nanosTimeout)返回了剩余等待的时间;await(long time, TimeUnit unit)返回boolean值,如果返回false,则说明是因为超时返回的,否则返回true。为什么增加返回值?为了就是帮助我们弄清楚方法返回的原因。


四. 阿里多线程考题


最后我们通过实现了Lock和Condition接口能力的ReentrantLock类来解决阿里多线程面试题。

题目是使用三个线程循环打印ABC,一共打印50次。我们直接上答案:

37.png38.png39.png


五. 总结


Lock与Condition接口就说完了,最后指北君再总结一下:

针对synchronized内置锁无法解决死锁、只有一个条件变量等问题,Doug Lea在Java并发包中增加了Lock和Condition接口来解决。对于死锁问题,Lock接口增加了超时、响应中断、非阻塞三种方式来获取锁,从而避免了死锁。针对一个条件变量问题,Condtition接口通过一把锁可以创建多个条件变量的方式来解决。最后我们通过一个阿里面试题来说明了Lock和Condition接口所提供的能力。

今天就到这了,我是指北君,我们下篇文章见~


相关文章
|
1月前
|
存储 关系型数据库 MySQL
阿里面试:为什么要索引?什么是MySQL索引?底层结构是什么?
尼恩是一位资深架构师,他在自己的读者交流群中分享了关于MySQL索引的重要知识点。索引是帮助MySQL高效获取数据的数据结构,主要作用包括显著提升查询速度、降低磁盘I/O次数、优化排序与分组操作以及提升复杂查询的性能。MySQL支持多种索引类型,如主键索引、唯一索引、普通索引、全文索引和空间数据索引。索引的底层数据结构主要是B+树,它能够有效支持范围查询和顺序遍历,同时保持高效的插入、删除和查找性能。尼恩还强调了索引的优缺点,并提供了多个面试题及其解答,帮助读者在面试中脱颖而出。相关资料可在公众号【技术自由圈】获取。
|
13天前
|
SQL 关系型数据库 MySQL
阿里面试:1000万级大表, 如何 加索引?
45岁老架构师尼恩在其读者交流群中分享了如何在生产环境中给大表加索引的方法。文章详细介绍了两种索引构建方式:在线模式(Online DDL)和离线模式(Offline DDL),并深入探讨了 MySQL 5.6.7 之前的“影子策略”和 pt-online-schema-change 方案,以及 MySQL 5.6.7 之后的内部 Online DDL 特性。通过这些方法,可以有效地减少 DDL 操作对业务的影响,确保数据的一致性和完整性。尼恩还提供了大量面试题和解决方案,帮助读者在面试中充分展示技术实力。
|
15天前
|
Java
java线程接口
Thread的构造方法创建对象的时候传入了Runnable接口的对象 ,Runnable接口对象重写run方法相当于指定线程任务,创建线程的时候绑定了该线程对象要干的任务。 Runnable的对象称之为:线程任务对象 不是线程对象 必须要交给Thread线程对象。 通过Thread的构造方法, 就可以把任务对象Runnable,绑定到Thread对象中, 将来执行start方法,就会自动执行Runable实现类对象中的run里面的内容。
32 1
|
21天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
44 4
|
1月前
|
消息中间件 存储 canal
阿里面试:canal+MQ,会有乱序的问题吗?
本文详细探讨了在阿里面试中常见的问题——“canal+MQ,会有乱序的问题吗?”以及如何保证RocketMQ消息有序。文章首先介绍了消息有序的基本概念,包括全局有序和局部有序,并分析了RocketMQ中实现消息有序的方法。接着,针对canal+MQ的场景,讨论了如何通过配置`canal.mq.partitionsNum`和`canal.mq.partitionHash`来保证数据同步的有序性。最后,提供了多个与MQ相关的面试题及解决方案,帮助读者更好地准备面试,提升技术水平。
阿里面试:canal+MQ,会有乱序的问题吗?
|
27天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
1月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
20 3
|
1月前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
37 2
|
1月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
30 2
|
1月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
34 1
下一篇
无影云桌面