RD 面试小技巧之PL篇(Java:JVM, NIO, 并发编程)

1. JVM

关键点

  • 定位(JVM 是基于栈的计算机器;栈操作指令,不与 JAVA 直接对应,属于 JAVA 四类指令之一)
  • 运行时状态(每个运行时的线程有一个独享的 JVM Stack,存储 Frame;逻辑概念“栈帧” = 操作数栈 + 局部变量数组 + class引用,每一次方法调用 JVM 会自动创建一个栈帧)
  • 类加载器:类加载的过程 = 加载 + 验证 + 准备 + 解析 + 初始化;类加载的时机(显示调用,隐式调用,不会初始化的时机);类加载器的分类(BootstrapClassLoader——在JVM内部,是 JDK 最核心类;ExtClassLoader 扩展类加载器;AppClassLoader 应用类加载器);类加载器的特点(双亲委托,负责依赖,缓存加载)。
  • 内存模型:调用栈 Stack Trace(由多个栈帧组成;一个线程对应一个线程栈/JAVA方法栈,每个线程都只能访问自己的线程栈;资源的隔离性——每执行一个方法,就给当前方法创建一个栈帧,所有原生类型的局部变量和对象的引用地址都存储在线程栈中),堆内存(又称“共享堆”,所有线程共享堆内存,堆内存包含所有 JAVA 代码中创建的对象内容;GC 负责创建、回收、销毁堆内存上的对象;分类——年轻代 Young-Gen & 老年代 Old-Gen,年轻代包括新生代 Eden sapce、存活区一 S1、存活区零 S0),非堆 Non-heap(存放元数据,包括 Metaspace, Compressed Class Space, Code Cache),JVM 自身堆外(JVM 直接使用的计算机内存空间)。
  • 启动参数:系统属性,运行模式,堆内存设置(xmx, xms, xmn, meta, xss),GC 设置,分析诊断,JavaAgent。

经验认识

  • 内存模型————JVM除了使用堆内内存外,还有一些非堆和对外内存,因此Xmx不可设置的过大,谨防OOM内存溢出。
  • 内存模型————实际应用中可以把每个应用程序运行在VM或Docker里,资源隔离,不会互相抢占内存资源;指定 Xmx 为整个操作系统的 60%-80%,留出余量。

2. NIO

关键点:大规模的并发 IO 挑战

问题——>解决方案

  • 线程——>线程池
  • CPU 使用率——>减少 CPU 等待时间
  • 数据来回大量复制——>共享内存 & IO模型

核心技术:NIO(非阻塞I/O模型)

  • Websocket 协议(Server 端给不同的 Client 端大量推送消息)
  • BIO(阻塞I/O模型):server 一旦接收到一个 client 连接请求,建立通信socket进行读写操作;JVM 进程阻塞,此时不能再接收请求。用户进程等待内核进程 data copy 后唤醒,然后处理数据。
  • NIO(非阻塞I/O模型):用户进程发起系统调用,轮询(即非阻塞)查看 data 是否 READY;READY 后开始和 BIO 类似的阻塞 IO 处理,阻塞时间较短。帮助提高系统吞吐量。
  • IO Multiplexing(I/O多路复用模型):将维护网络连接和处理数据两个流程分开,由不同线程处理,IO 处理流水线化。两个阻塞点,一是 fd 集合在用户态和内核态间来回拷贝(解决方案:epoll——用户态、内核态共享一块内存,通过回调解决遍历,fd 集合数量无限制),二是 IO 操作的后半阶段。基于 Reactor 模式,屏蔽了用户态和内核态交互的中间过程。
  • 信号驱动的I/O模型:基于 EDA(Event-Driven Architecture,事件驱动架构),网络请求由 handler 变为一个事件分发给多个线程处理;无需轮询,减少运行时等待,数据 READY 时 kernel 会发信号(后续由用户进程做data copy)。
  • 异步I/O模型:(阶段一)用户进程发出系统调用,返回;(阶段二)data READY,kernel 进行 data copy,然后发信号告诉用户进程 data 准备完毕。

标准框架:Netty(JAVA 网络应用编程首选框架)

  • 内部设计实现:核心(动态可扩容的 Zero-Copy-Capable Rich Byte Buffer + 通用 API + 可扩展事件模型),传输服务层,协议支持层。
  • 特性:异步,事件驱动,基于 NIO
  • 优点:高性能(高吞吐,低延迟,低开销,零拷贝,可扩容),松耦合,易用
  • 核心概念:Channel(一个打开的连接,可读可写数据,非阻塞), ChannelFuture(用于获取连接的状态;可添加回调方法), Event & Handler(事件处理器关联入站出站数据流), Encoder & Decoder(用于序列化&反序列化), ChannelPipeline(事件处理器链,流水线化顺序处理,不同场景流程不同)
  • Netty 应用 = 网络事件 + 应用程序逻辑事件 + 事件处理程序(入站事件,出站事件,接口,适配器)
  • Reactor 模式(一个 Service Handler,多个 Event Handler):事件驱动,多路复用。Netty 支持三种模型,
    • 单线程模型:selector 身兼数职,负责 I/O 和负责业务的都是 reactor thread。
    • 多线程模型:I/O (reactor thread 处理,负责维护 socket 和分发事件) 和业务(worker thread pool 处理)在线程层面做了隔离。
    • 主从模型:主线程(mainReactor,负责维护网络连接),从线程(subReactor,负责事件分发)workder thread pool 负责业务处理,reactor thread pool 负责 I/O 操作。
  • 关键对象
    • Bootstrap:启动线程。
    • NioEventLoopGroup:支持三种 Reactor 模型。
    • NioEventLoop:单线程,包含一个 selector;一个 EventLoop 可以绑定多个 SocketChannel,负责整个IO事件的生命周期(轮询监听 IO 事件,处理 IO 事件,处理任务队列)。
    • SocketChannel:handler 集合。
    • ChannelInitializer:绑定 handler 集合和 channel
    • ChannelPipeline:事件处理器链。
    • ChannelHandler:事件处理器。
      • 入站:channel --> ChannelInboundHandler --> 应用程序
      • 出站:应用程序 --> ChannelOutboundHandler --> channel

典型应用:API 网关(请求接入 + 业务聚合 + 中介策略 + 统一管理)

  • 流量网关:外层屏障,与业务无关;关注微服务集群,对性能有高要求。
    • 常见框架之 OpenResty:基于Nignx插件机制,引入Lua引擎,可编程,扩大了应用场景
    • 常见框架之 Kong:进一步完善了 OpenResty 的插件机制,提供开箱即用的模块,有控制台
  • 业务网关:关注业务,提供针对性的服务级别的相关操作(细粒度流控、聚合、发现、校验、过滤等处理)。
    • 常见框架之 Spring Cloud Gateway:任何依托于Spring Web的项目可以通过映射URL添加网关能力
    • 常见框架之 Zuul
      • Zuul:
        • 请求流程:HTTP Request -> pre filter -> routing filter (router) -> origin servers
        • 响应流程:origin servers -> post filter -> client
      • Zuul 2.x
        • Netty 重构版:内部BIO换成了NIO
        • Netty HTTP Server:网关接入端
        • Netty Client Handlers:接出端
        • Inbound filter:请求流程
        • Outbound filter:响应流程
        • Endpoint filter:路由
    • 常见框架之 Soul:整合了主流的微服务框架,开箱即用,有管理控制台配置网关能力

经验认识

高性能————高并发用户(大量并发业务连接),高吞吐量(单位时间内能处理较多的业务),低延迟(每个请求的处理时间较低),容量(超出上线有破坏性作用)

  • 弊端:系统复杂度、建设维护成本、故障的破坏性均会大幅增加。
  • 应对:限制容量,控制爆炸半径,工程积累与改进。
  • 性能优化入手阶段:网络连接,数据准备,事件分发。

Netty 优化

  • 永远不要阻塞 EventLoop!
  • 系统参数优化
  • 缓冲区优化:给 Bootstrap 绑定缓冲区;复用挥手状态未完全关闭的连接。
  • 心跳周期优化:短线重连(快速恢复网络,提升可用性),心跳机制(无高频数据包传输时主动探活)。
  • Byte Buffer 优化
  • 其他:ioRatio(IO操作和业务操作的资源消耗比),Watermark(压力水位),TrafficShaping(网络流控保险丝)。

API 网关架构设计

  • 思路:由简到繁,先实现业务核心框架,再在技术复杂度和业务复杂度上分别提升。
  • 流程:抽象(子组件和关键对象) -> 依赖(组件间的关系) -> 组件化 -> 拼成整体

3. 并发编程

关键点:Moore's Law 失败,“多核 + 分布式”时代来临

多线程

  • 线程
    • 核心状态 RWB:READY(就绪;拿到锁,解除等待), Runnable(执行 start() 启动新线程后,拿到 CPU 时间片前), RUNNING(抢到了 CPU 时间片的线程运行), WAITING / TIMED_WAITING(主动等待), Blocked(被动等待;遇到同步代码块/锁,被通知).
    • 创建:Runnable 接口(重载 run() 方法,定义当前线程里的一个任务),Thread 类(继承了 Runnabele 接口;调用 start() 方法,JVM 真正创建一个 OS 线程;重载 run() 方法,定义当前线程里的一个任务)。
    • 方法:run, start, join, sleep, wait, notify / notifyAll
    • 多线程间统一协调调度: wait, notify / notifyAll
    • 中断处理:方法一,interrupt() + InterruptedException 异常;方法二,设置一个外部的全局状态。
  • 线程安全
    • 问题描述:不同线程竞争/同步相同资源。若资源的读写顺序敏感,则存在“竞态条件”(临界区:导致竞态条件发生的代码区)。
    • 并发相关性质:原子性,可见性(关键字:volatile,保证当前变量的更新立刻被更新到主内存,各线程的即时修改同步在各自的副本上;关键字 synchronized 和接口 Lock,保证同一时刻只有一个线程获取锁然后执行同步代码,释放锁前变量的所有修改刷新到主内存),有序性(“happens-before 原则”,便于在多线程代码运行时设置锚点)。
  • 线程池
    • 出发点:线程属于重量级资源;CPU 核心数有限,线程的物理资源也有限;线程过多,上下文切换开销大,并行效率下降。
    • Executor 接口:顶层,包含一个无返回值的 execute(Runnable task) 方法。
    • ExecutorService 接口:继承 Executor 接口。shutdown() 方法,三个 submit() 方法。在 submit() 调用过程中,另一个线程的返回值 & 异常均可捕获。
    • ThreadPoolExecutor:(核心步骤)addWorder(task, true) --> workQueue.offer(task) --> addWorder(task, false)。首先判断正在执行的线程数量是否达到线程池的“核心线程数”;未达到,创建新线程处理任务;达到,放入工作队列/缓冲队列(BlockingQueue)。缓冲队列满时,判断是否达到“最大线程数”;未达到,创建新线程处理任务;达到(此时新线程可以复用已释放的 CPU 时间片),执行拒绝策略(不显式定义则使用默认策略)。
    • ThreadFactory:批量在线程池中创建具有相同配置、特定属性的一组线程。
    • Executors 工具类
    • 基础接口:Callable(call() 方法,得到有泛型的返回值),Future(对应异步执行的一个任务,最终需要拿到返回值;get() 方法的两个重载,一个是预期异步线程很快执行完,一个是异步线程的执行时长不确定)。

并发

  • JAVA 并发包 JUC (java.util.concurrency)
    • 5类核心功能:锁,原子类,线程池,工具类,集合类。
    • 5类接口:锁机制类(Lock, Condition, ReentrantLock, ReadWriteLock, LockSupport),原子操作类(AtomicInteger, AtomicLong, LongAdder),线程池相关类( Future, Callable, Executor, ExecutorService),信号量工具类(CountDownLatch, CyclicBarrier, Semaphore),并发集合类(CopyOnWriteArrayList, ConcurrentMap)。
  • 锁(interface Lock)
    • 工具包:java.util.concurrent.locks
    • 出发点:显示的锁(更灵活) vs { wait & nofity 机制,synchronized 同步块机制}
    • interface Lock
      • 基础实现类:ReentrantLock,公平锁,非公平锁。
      • ReadWriteLock 读写锁:(场景)并发读、并发写(需要保证写期间数据的一致性),读多写少。
    • interface Condition
      • 一个锁可以有多个 Condition
    • LockSupport:静态方法
    • 最佳实践
      • 永远只在更新对象的成员变量时加锁
      • 永远只在访问可变的成员变量时加锁
      • 永远不在调用其他对象的方法时加锁(外部加锁,无法控制锁粒度)
    • 原则————“最小使用锁范围”
      • 降低锁范围:降低锁定代码的作用域(提升整体的运行效率)
      • 细分锁粒度:一个大锁拆分成多个小锁(提升并发能力)
  • 并发原子类
    • 工具包:java.util.concurrent.atomic
    • 出发点:显示的锁 + { wait & nofity 机制,synchronized 同步块机制},本质都是操作的串行化,相当于单线程处理,本质上未并发执行。
    • 底层原理:CAS 机制(CompareAndSwap,相当于乐观锁,通过自旋重试保证写入)。
    • 适用场景:并发压力一般时,无锁更快(大部分时候都是一次写入,并发性能提升。并发压力较大时,本地不停自旋会占用大量资源;较小时,是否使用 CAS 影响不大)。
    • 改进:分段思想(将读写竞争热点 value 拆分成和线程数一样多的数组 cell[],按线程数分段;每个线程写自己的 cell[i],最后对数组求和)。
  • 并发工具类
    • 面向更复杂多线程协作的场景
    • 基于队列同步器 AQS(AbstractQueuedSynchronizer,构建锁和并发工具类的基础,JUC 的核心组件)实现的并发工具类:抽象了竞争的资源和线程队列;更灵活、更细粒度
      • Semaphore:对当前进入队列的线程,同一时间下的并发线程数控制。
      • CountDownLatch:阻塞主线程,子线程均满足条件时,主线程继续。达到聚合点后不可复用。
    • CyclicBarrier:任务执行到一定阶段,等待其他任务对齐(阻塞各子线程,回调聚合);阻塞 N 个线程时所有线程被唤醒继续。达到聚合点后,可循环使用(计数为 0 时重置为 N)。
    • Future 模式:Future / FutureTask / CompletableFuture
      • 单个线程/任务的执行结果:Future / FutureTask
      • 多个异步结果的组合、封装,异步的回调处理:CompletableFuture
  • 并发集合类
    • List
      • 分类:ArrayList, LinkedList(均存在写冲突和读写冲突)
      • 线程安全问题解决方案
        • 读写操作加锁(大锁)
        • CopyOnWriteArrayList 类:对写加锁(串行写;每个线程写在各自副本上,写完替换原容器中引用的指针),对读采取快照思维(每个线程无锁并发读原容器)。实现读写分离,保证最终一致。
    • Map
      • 分类:HashMap, LinkedHashMap, ConcurrentHashMap
      • 写冲突 + 读写冲突 + keys()无序:HashMap, LinkedHashMap
      • 改进:ConcurrentHashMap(原理————“分段锁”:一个大的 HashMap 中默认定义16个 segment,即并发级别 concurrentLevel=16,降低了大锁的粒度。并发级别可调,最多允许16个线程并发地操作16个 segment)。
        • 退化(所有并发线程都focus在同一个segment):HashMap + 大锁。
    • 解决方案总结
      • ArrayList, LinkedList:采用“副本机制”
      • HashMap, LinkedHashMap:采用分段锁或 CAS 机制

经验认识

线程安全问题解决方案

  • 思路:减少锁的粒度,增加并发粒度
  • 方案
    • 一————同步块(关键字:synchronized),操作结果对其他线程可见。执行粒度:方法,对象(偏向锁,轻量级锁/乐观锁,重量级锁)。
    • 二————volatile(场景:单个线程写,多个线程读),操作前对操作后可见。替代方案:Atomic 原子操作类。
    • 三————final(场景:仅可读,跨线程安全)

四种经典利器

  • ThreadLocal 类(针对并发的线程安全问题。在当前线程内进行变量和数据的传递;同一线程跨方法调用栈的调用,在最外层将要操作的数据放入 ThreadLocal 实例)
  • Stream in JDK8(流水线化的处理模型,将批量数据的单线程处理和多线程并行处理在接口层面做了统一)
  • 伪并发问题
  • 分布式下的锁和计数器(分布式环境下应考虑并行,超出了线程的协作机制)

加锁前的考虑

  • 粒度:能小则小(意味着大部分代码可以并发执行)
  • 性能(提升效率)
  • 重入(防止线程卡死)
  • 公平(防止线程饿死)
  • 自旋锁(Spinlock,大大降低使用锁的开销)
  • 场景:必须基于业务场景!

线程间协作与通信

  • 共享数据和变量
  • 线程协作
  • 进程协作
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值