2021-Java后端工程师面试指南-(并发-多线程)(上)

简介: 前言文本已收录至我的GitHub仓库,欢迎Star:github.com/bin39232820…种一棵树最好的时间是十年前,其次是现在

Tips


面试指南系列,很多情况下不会去深挖细节,是小六六以被面试者的角色去回顾知识的一种方式,所以我默认大部分的东西,作为面试官的你,肯定是懂的。

www.processon.com/view/link/6…

上面的是脑图地址


叨絮


可能大家觉得有点老生常谈了,确实也是。面试题,面试宝典,随便一搜,根本看不完,也看不过来,那我写这个的意义又何在呢?其实嘛我写这个的有以下的目的

  • 第一就是通过一个体系的复习,让自己前面的写的文章再重新的过一遍,总结升华嘛
  • 第二就是通过写文章帮助大家建立一个复习体系,我会将大部分会问的的知识点以点带面的形式给大家做一个导论

然后下面是前面的文章汇总

今天来看看多线程的,这块是重点,也是难点,硬核有点多哈哈。


并发


记得阿里的第一个题就是面的并发,哈哈 这个小六六得好好总结了。


聊聊Java的并发模型

这个为啥是第一个问题,肯定是有原因的,如果连Java的并发模型都不清楚,你跟我扯一堆的锁,一堆的juc有啥用呢?

  • Java并发 采用的是 共享内存模型,Java线程之前的通信总是隐式进行的。
  • Java线程通信由Java内存模型(简称 JMM)控制,JMM 决定一个线程对共享变量的写入何时对另一个线程可见。从抽象角度看,JMM定义了 线程 和 主内存 之间的抽象关系:线程之间的共享变量储存在主内存中,每个线程都有一个私有的本地内存,本地内存储存了 该线程 以读共享变量的副本。


对多线程了解吗,说说你平时怎么对临界资源的访问控制的。

其实这个题就是一个引人,由浅入深的过程,

  • 如果对应的临界资源是在单JVM的进程中,那么我们可以用Synchronized和lock
  • 对于分布式环境下的多线程中,那么就得用上分布式锁(redis 或者zookeeper实现)


那么聊聊你对Synchronized的认识吧

  • synchronized 关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
  • synchronized 最主要三种用法
  • 修饰实例方法 要获得当前对象实例的锁
  • 修饰静态方法 获得当前类对象的锁
  • 修饰代码块 synchronized(this|object) 表示进入同步代码库前要获得给定对象的锁。synchronized(类.class) 表示进入同步代码前要获得 当前 class 的锁
  • synchronized 关键字最主要的二种底层实现方式:
  • synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
  • synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。


聊聊Java对象的布局

  • 首先我们知道Java对象分布由三个部分组成 对象头、实例数据、对对齐填充字节,下面我们来一个个说说
  • 对象头的组成由 Mark Word、类元数据的指针(Klass Pointer)、数组长度(不一定有),在64位Java虚拟机里面的Mark word  包含了我们的 hashcode的值  我们的分代年龄 锁标志位等
  • 实例数据 并不是所有的变量都存放在这里,对象的的所有成员变量以及其父类的成员变量是存放在这里的。
  • JVM要求Java对象的大小必须是8byte的倍数,所以这个的作用就是把对象的大小补齐至8byte的倍数。


那你说说Synchronized锁升级的过程吧

锁级别从低到高依次是:

  • 无锁状态
  • 偏向锁状态
  • 轻量级锁状态
  • 重量级锁状态

小六六在这给大家明确的一点就是,Synchronized锁的是对象而不是其包裹的代码。

  • 对象被new出来后,没有任何线程持有这个对象的锁,这时就是无锁状态;Mark Word锁标识是01
  • 当且仅当只有一个线程A获取到这个对象的锁的时候,对象就会从无锁状态升级成为偏向锁,Mark Word中就会记录这个线程的标识(锁标识是01),此时线程A持有这个对象的锁;
  • 还是这个线程A再次获取这个对象的锁时,发现他是一个偏向锁,并且对象头中记录着自己的线程标识,那么线程A就继续使用这把锁(不需要cas去获取锁了)。这里也是锁的可重入性,所以,synchronized也是可重入锁;
  • 在线程A持有锁的时候,线程B也来争抢这把锁了,线程B发现这是一把偏向锁,并且对象头中的线程标识不是自己。那么首先进行偏向锁的撤销过程,然后偏向锁就会升级为轻量级锁,此时Mark Word 的锁标识是00
  • 又有一些线程来争抢这个轻量级锁了,争抢的过程其实就是利用CAS自旋。为了避免长时间自旋消耗CPU资源,当自旋超过10次的时候,轻量级锁升级为重量级锁(其他线程阻塞,不会耗费cpu)。此时Mark Word的锁标识是10


可以聊聊CAS吗,它有什么问题吗?

CAS,compare and swap的缩写,就是一个保证原子性的一个手段,CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。


问题

  • ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
  • 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
  • 只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。


可以说说Synchronized和Lock的区别嘛

  • 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
  • synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
  • synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可中断、可公平(两者皆可)
  • synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。
  • 而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。


既然提到了Lock,那我们来聊聊他最常用的实现ReentrantLock吧,说说的公平和非公平是怎么实现的,他们的哪个效率高,默认是哪个,又是怎么现实可重入的

  • 首先公平和非公平是指多线程下各线程获取锁的顺序,先到的线程优先获取锁,而非公平锁则无法提供这个保障,但是呢 我们知道ReentrantLock的非公平实现,其实并不是随机的,它是有一定顺序的非公平,举个非公平的例子,假设A来获取锁,如果A获得了锁,此时B来获取锁,然后B失败了,B就去队列等待,此时C来了,然后C也失败了,他也去等待,此时D过来了,然后A释放了锁,那你说如果是绝对公平的话这个时候应该是B获取锁才对,但是源码中是D此时有机会去获取锁,所以它是一定顺序的非公平,非公平锁效率高于公平锁的,因为非公平锁减少了线程挂起的几率,后来的线程有一定几率逃离被挂起的开销,所以默认是非公平的锁。而我们的Synchronized也是非公平的
  • 可重入是指,当我一个线程获取了这把锁,下次当前线程再释放锁之前再去获取锁的时候是可以成功,这就是可重入锁,Lock 的实现是判断是当前线程的时候,给锁状态+1,然后我们的Synchronized也是可重入的
相关文章
|
4天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
23 9
|
3天前
|
存储 网络协议 安全
30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场
本文精选了 30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场。
13 2
|
7天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
4天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
6天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
7天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
17 1
|
6月前
|
数据可视化 Java 测试技术
Java 编程问题:十一、并发-深入探索1
Java 编程问题:十一、并发-深入探索
75 0
|
3月前
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
69 1
|
11天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
2月前
|
Java API 容器
JAVA并发编程系列(10)Condition条件队列-并发协作者
本文通过一线大厂面试真题,模拟消费者-生产者的场景,通过简洁的代码演示,帮助读者快速理解并复用。文章还详细解释了Condition与Object.wait()、notify()的区别,并探讨了Condition的核心原理及其实现机制。