大厂面试 | Java多线程,被面试官难倒了!

136 阅读5分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。

本期是【大厂面试】系列文章的第2

面试现场

面试官看你简历上写了精通多线程,来聊聊,为什么要使用多线程呢?

独白:精通...您不会真信了吧...

大彬:使用多线程最主要的原因是提高系统的资源利用率。

大彬:多个线程同时运行,可以减少线程上下文切换的开销,提高并发的能力和CPU的利用效率。

大彬:在平时工作中多线程也是常见的。比如Tomcat每处理一个请求都会从线程连接池里取一个线程去处理。

面试官嗯,那什么场景可以使用多线程呢?

大彬:一些并发量大的场景,比如读入大量文件写入数据库,使用多线程能够极大提高效率。

大彬:耗时比较长的任务,比如用户注册之后发送邮件、短信之类的操作,这种任务就算失败了也不是特别重要,可以使用异步线程去处理。

大彬:定时任务,比如定期更新配置文件、备份数据之类的任务。

面试官嗯,平时在使用多线程的时候,可能会遇到线程安全的问题吧。讲讲什么是线程安全?

大彬:我是这么理解的,当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。

面试官那你平时怎么处理线程安全问题的?

独白:幸亏看了【面试笔记】,不慌哈哈

大彬:这个还得具体问题具体分析。首先判断有没有线程安全问题,若有则根据具体的情况去处理线程安全的问题。

大彬:比如涉及到操作的原子性,可以考虑使用原子类。

大彬:如果涉及到对线程的控制,可以考虑线程工具类CountDownLatch/Semaphore等等。

大彬:集合类的话,考虑java.util.concurrent包下的集合类。

大彬:还有synchronizedlock包下的类,redis分布式锁等。

面试官:刚刚提到synchronized,讲讲它的底层原理?

独白:底层原理...裂开

大彬synchronized 同步代码块的实现是通过 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor 的持有权。

独白:monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,这也是为什么Java中任意对象可以作为锁的原因。

大彬:其内部包含一个计数器,当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在 执行 monitorexit 指令后,将锁计数器设为0 ,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。

大彬synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

面试官:那synchronized和ReentrantLock有什么区别呢?

  1. 使用synchronized关键字实现同步,线程执行完同步代码块会自动释放锁,而ReentrantLock需要手动释放锁。
  2. synchronized是非公平锁,ReentrantLock默认是非公平锁,可以设置为公平锁。
  3. ReentrantLock上等待获取锁的线程是可中断的,线程可以放弃等待锁。而synchonized会无限期等待下去。
  4. ReentrantLock 可以设置超时获取锁。在指定的截止时间之前获取锁,如果截止时间到了还没有获取到锁,则返回。
  5. ReentrantLock 的 tryLock() 方法可以尝试非阻塞的获取锁,调用该方法后立刻返回,如果能够获取则返回true,否则返回false。

面试官:什么叫公平锁和非公平锁?

大彬:如果是按照线程访问顺序去获取对象锁,则为公平锁,否则为非公平锁。

面试官:知道什么是CAS吗?

大彬:嗯,CAS全称Compare And Swap,比较与交换,是乐观锁的主要实现方式。CAS 在不使用锁的情况下实现多线程之间的变量同步。ReentrantLock 内部的 AQS 和原子类内部都使用了 CAS。

大彬:CAS算法涉及到三个操作数:需要读写的内存值 V;进行比较的值 A;要写入的新值 B。

大彬:只有当 V 的值等于 A 时,才会使用原子方式用新值B来更新V的值,否则会继续重试直到成功更新值。

大彬:以 AtomicInteger 为例,AtomicInteger 的 getAndIncrement()方法底层就是CAS实现,关键代码是 compareAndSwapInt(obj, offset, expect, update),其含义就是,如果obj内的valueexpect相等,就证明没有其他线程改变过这个变量,那么就更新它为update,如果不相等,那就会继续重试直到成功更新值。

面试官:基础掌握的不错,今天面试就到这吧~