synchronized解析及锁膨胀过程,面试再也不怕了

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: synchronized解析及锁膨胀过程,面试再也不怕了


一、简单概述


synchronized是Java为多线程并发情况提供的一种保障共享资源安全的一个关键字,它需要作用于对象上。


1.1 synchronized特性

1.1.1 原子性

原子性是指一个操作或者一组操作,要么全部执行且执行过程不会被任何因素打断,要么就都不执行。


synchronized 对临界资源加锁时,保证了临界资源对于线程间的原子性。比如高清无码资源被线程A拿到,并且因为资源非常好所以线程 A 多次进入资源进行使用(可重入),但是此时线程 B 也想来观摩观摩,但不好意思资源已被 A 占用,即使 A 退出资源一次但还没有全部退出时,线程 B 只能甘等。


1.1.2 有序性

有序性值程序执行的顺序按照代码先后执行


Java允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性。synchronized保证了每个时刻都只有一个线程访问同步代码块,也就确定了线程执行同步代码块是分先后顺序的,保证了有序性。


1.1.3 可见性

可见性是指多个线程访问一个资源时,该资源的状态、值信息等对于其他线程都是可见的


synchronized对一个类或对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的,并且在释放锁之前会将对变量的修改刷新到主存当中,保证资源变量的可见性,如果某个线程占用了该锁,其他线程就必须在锁池中等待锁的释放。


1.1.4 重入性

当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁。通俗一点讲就是说一个线程拥有了锁仍然还可以重复申请锁。


1.2 用法

synchronized 一共有三种使用方法并且锁的对象各不相同:


1.2.1 直接修饰实例方法

在这种情况下多线程并发访问实例方法时,如果其他线程调用同一个对象的被 synchronized 修饰的方法,就会被阻塞。


相当于把锁记录在这个方法对应的实例对象上。


public synchronized void set(){
    i++;
}


1.2.2 直接修饰静态方法

在这种情况下进行多线程并发访问时,如果其他线程也是调用属于同一类的被 synchronized 修饰的静态方法,就会被阻塞。


相当于把信息记录在这个方法对应的上。


public synchronized static  void set(){
    i++;
}


1.2.3 直接修饰代码块

如果此时有别的线程也想访问某个被 synchronized(对象A) 修饰的同步代码块时,也会被阻塞。


同步代码块会把记录在对象A


public void set(){
    synchronized(对象A){
        i++
    }
}


synchronized的三种锁示意图:


image.png


二、原理实现


在理解锁实现原理之前先了解一下Java的对象头和Monitor


2.1 对象头

首先我们来看看对象的内存布局


image.png


内存中的对象一般由三部分组成,分别是对象头、对象实际数据和对齐填充。大家可以看看我的这篇博客详细了解👉《链接》


对象头包含 Mark Word(对象头)、Class Pointer(类型指针)和 Length(数组长度) 三部分。


Mark Word:记录了对象关于锁的信息,垃圾回收信息等。


Class Pointer:用于指向对象对应的 Class 对象(其对应的元数据对象)的内存地址。

Length:只适用于对象是数组时,它保存了该数组的长度信息。


我们刚才讲的锁 synchronized 锁使用的就是对象头的 Mark Word 字段中的一部分,Mark Word 中的某些字段发生变化,就可以代表锁不同的状态。


由于锁的信息是记录在对象里的,有的开发者也往往会说锁住对象这种表述。


在32位和64位操作系统上 Mark Word 的结构图


image.png


2.2 Monitor

Monitor是一种用来实现同步的工具,与每个 Java 对象相关联,所有的 Java 对象是天生携带 Monitor。


Monitor是实现 Synchronized(内置锁) 的基础。


对象的监视器(Monitor)由 ObjectMonitor 对象实现(C++),其跟同步相关的数据结构如下:


ObjectMonitor() { 
    _header = NULL; 
    _count = 0;     //用来记录该对象被线程获取锁的次数
    _waiters = 0;
    _recursions = 0;  // 线程的重入次数
    _object = NULL;   // 存储该Monitor的对象 
    _owner = NULL;    // 标识拥有该Monitor的线程 
    _WaitSet = NULL;  // 处于wait状态的线程,会被加入到_WaitSet 
    _WaitSetLock = 0 ; 
    _Responsible = NULL; 
    _succ = NULL; 
    _cxq = NULL;    // 多线程竞争锁时的单向列表 
    FreeNext = NULL; 
    _EntryList = NULL;  // 处于等待锁block状态的线程,会被加入到该列表 
    _SpinFreq = 0; 
    _SpinClock = 0; 
    OwnerIsThread = 0; 
}


每一个Java对象都可以与一个监视器Monitor关联,我们可以把它理解成为一把锁(Monitor是重量级锁),当一个线程想要执行一段被synchronized圈起来的同步方法或者代码块时,该线程得先获取到synchronized修饰的对象对应的Monitor。


我们的Java代码里不会显示地去创造这么一个Monitor对象,我们也无需创建,事实上可以这么理解:Monitor并不是随着对象创建而创建的。我们是通过synchronized修饰符告诉JVM需要为我们的某个对象创建关联的Monitor对象。


每个线程都存在两个ObjectMonitor对象列表,分别为free和used列表。同时JVM中也维护着global locklist。当线程需要ObjectMonitor对象时,首先从线程自身的free表中申请,若存在则使用,若不存在则从global list中申请。


ObjectMonitor的数据结构中包含:_ owner、 _ WaitSet 和_ EntryList,它们之间的关系转换可以用下图表示:


image.png


2.3 实现原理

我们通过一个列子来反编译一下代码:


public class SynchronizedDemo {
    public void synchronizedTest(){
        synchronized (this){
            System.out.println("测试同步代码块");
        }
    }
}


反编译结果


image.png


Monitorenter:


每个对象有一个监视器锁(Monitor)。当Monitor被占用时就会处于锁定状态,线程执行Monitorenter指令时尝试获取Monitor的所有权(获得到的 Monitor会放在对象头中),过程:


如果Monitor的进入数为0,则该线程进入Monitor,然后将进入数设置为1,该线程即为Monitor的所有者。


如果线程已经占有该Monitor,只是重新进入,则进入Monitor的进入数加1。


如果其他线程已经占用了Monitor,则该线程进入阻塞状态,直到Monitor的进入数为0,再重新尝试获取Monitor的所有权。


Monitorexit:


执行Monitorexit的线程必须是objectref所对应的Monitor的所有者。


指令执行时,Monitor的进入数减1,如果减1后进入数为0,那线程退出Monitor,不再是这个Monitor的所有者。其他被这个Monitor阻塞的线程可以尝试去获取这个 Monitor 的所有权。


细心的小伙伴注意到,这里有两个 Monitorexit 指令,通常在正常退出的时候,要用一个Monitorexit,如果中间出现异常,锁会一直无法释放。所以编译器会为同步代码块添加了一个隐式的 try-finally 异常处理,在 finally 中会调用 Monitorexit 命令最终释放锁。


再来看看另一种加锁方式


public class SynchronizedDemo {
    public synchronized void synchronizedTest() {
        System.out.println("测试同步方法");
    }
}


反编译


image.png


从反编译的结果来看,其常量池中多了ACC_SYNCHRONIZED标示符,JVM就是根据该标示符来实现方法的同步的:


当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取Monitor,获取成功之后才能执行方法体,方法执行完后再释放Monitor。


总结一下:


Synchronized的底层是通过一个Monitor的对象来完成,wait/notify等方法也依赖于Monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。


三、锁膨胀升级过程


在 Java 的早期版本中,synchronized 会直接去获取 Monitor 的所有权而 Monitor 又属于重量级锁,这需要让线程从用户态转变为内核态而这个状态转换需要花费很多的处理器时间,甚至可能比用户代码执行的时间还要长。


所以由于种效率太低,在JDK1.6之后对 synchronized 做了优化。


Linux操作系统的体系架构分为:用户空间(应用程序的活动空间)和内核。


image.png


内核:本质上可以理解为一种软件,控制计算机的硬件资源,并提供上层应用程序运行的环境。


用户空间:上层应用程序活动的空间。应用程序的执行必须依托于内核提供的资源,包括CPU资源、存储资源、I/O资源等。


系统调用:为了使上层应用能够访问到这些资源,内核必须为上层应用提供访问的接口:即系统调用。


所有进程初始都运行于用户空间,此时即为用户运行状态(简称:用户态);但是当它调用系统调用执行某些操作时,例如 I/O调用,此时需要陷入内核中运行,我们就称进程处于内核运行态(或简称为内核态)。 系统调用的过程可以简单理解为:


用户态程序将一些数据值放在寄存器中, 或者使用参数创建一个堆栈, 以此表明需要操作系统提供的服务。


用户态程序执行系统调用。


CPU切换到内核态,并跳到位于内存指定位置的指令。


系统调用处理器(system call handler)会读取程序放入内存的数据参数,并执行程序请求的服务。


系统调用完成后,操作系统会重置CPU为用户态并返回系统调用的结果。


由此可见用户态切换至内核态需要传递许多变量,同时内核还需要保护好用户态在切换时的一些寄存器值、变量等,以备内核态切换回用户态。这种切换就带来了大量的系统资源消耗,这就是在synchronized未优化之前,效率低的原因。


虛拟机对 synchronized 的优化技术:偏向锁( Biased Locking )、轻量级锁( Lightweight Locking )和如适应性自旋(Adaptive Spinning)、锁消除( Lock Elimination)、锁粗化( Lock Coarsening )等


那我们来看看 synchronized 的锁膨胀升级的过程


无锁–》偏向锁–》轻量级锁–》重量级锁


3.1 偏向锁(Biased Locking)

偏向锁时Mark Word字段结构图(32位操作系统):


image.png


有研究表明,其实在大部分场景都不会发生锁资源竞争,并且锁资源往往都是由一个线程获得的。如果这种情况下,同一个线程获取这个锁都需要进行一系列操作,比如说CAS自旋,那这个操作很明显是多余的。偏向锁就解决了这个问题。其核心思想就是:一个线程获取到了锁,那么锁就会进入偏向模式,当同一个线程再次请求该锁的时候,无需做任何同步,直接进行同步区域执行。这样就省去了大量有关锁申请的操作。所以,对于没有锁竞争的场合,偏向锁有很好的优化效果。


偏向锁的申请流程:


访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01,确认为可偏向状态。

如果为可偏向状态,则判断线程ID是否指向当前线程,如果是,进入步骤5,否则进入步骤3。


如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行5;如果竞争失败,执行4。

如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。


执行同步代码。


微信图片_20220426005415.png


偏向锁的撤销在上述第四步骤中有提到。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。


偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。


偏向锁的适用场景


始终只有一个线程在执行同步块,在它没有执行完释放锁之前,没有其它线程去执行同步块,在锁无竞争的情况下使用,一旦有了竞争就升级为轻量级锁,升级为轻量级锁的时候需要撤销偏向锁。在有锁的竞争时,偏向锁会多做很多额外操作,尤其是撤销偏向锁的时候会导致进入安全点,安全点会导致STW,导致性能下降,这种情况下应当禁用。所以一般JVM并不是一开始就开启偏向锁的,而是有一定的延迟,这也就是为什么会有无锁态的原因。


可以使用-XX:BiasedLockingStartupDelay=0来关闭偏向锁的启动延迟, 也可以使用-XX:-UseBiasedLocking=false来关闭偏向锁。👉偏向锁撤销导致的STW


通过加偏向锁的方式可以看到,对象中记录了获取到对象锁的线程ID,这就意味如果短时间同一个线程再次访问这个加锁的同步代码或方法时,该线程只需要对对象头Mark Word中去判断一下是否有偏向锁指向它的ID,有的话就继续执行逻辑了,没有的话,会CAS尝试获得锁,如果持有锁的线程在全局安全点检查时,不需要再使用该锁了则获取成功,程序继续执行,反之则获取锁失败,撤销偏向状态,升级为轻量级锁,即自旋锁。


3.2 轻量级锁(Biased Locking)即自旋锁

当有另外一个线程竞争获取这个锁时,由于该锁已经是偏向锁,当发现对象头 Mark Word 中的线程 ID 不是自己的线程 ID,销偏向锁状态,将锁对象Mark Word中30位修改成指向自己线程栈中Lock Record的指针(CAS抢)执行在用户态,消耗CPU的资源(自旋锁不适合锁定时间长的场景、等待线程特别多的场景)。


轻量级锁时Mark Word字段结构图(32位操作系统):


image.png


自旋策略


JVM 提供了一种自旋锁,可以通过自旋方式不断尝试获取锁,从而避免线程被挂起阻塞。这是基于大多数情况下,线程持有锁的时间都不会太长,毕竟线程被挂起阻塞可能会得不偿失。


自适应自旋锁


JDK 1.6引入了更加聪明的自旋锁,叫做自适应自旋锁。他的自旋次数是会变的,我用大白话来讲一下,就是线程如果上次自旋成功了,那么这次自旋的次数会更加多,因为虚拟机认为既然上次成功了,那么这次自旋也很有可能会再次成功。反之,如果某个锁很少有自旋成功,那么以后的自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。 大家现在觉得没这么low了吧。


轻量级锁的加锁过程:


在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。


拷贝对象头中的Mark Word复制到锁记录中;


拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word中的62位更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤4,否则执行步骤5。


如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态。


如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。此时为了提高获取锁的效率,线程会不断地循环去获取锁,这个循环是有次数限制的, 如果在循环结束之前CAS操作成功,那么线程就获取到锁,如果循环结束依然获取不到锁,则获取锁失败,对象的Mark Word中的记录会被修改为指向互斥量(重量级锁)的指针,锁标志的状态值变为10,线程被挂起,后面来的线程也会直接被挂起。


微信图片_20220426005551.png


轻量级锁的释放


释放锁线程视角(类比线程一):由轻量锁切换到重量锁,是发生在轻量锁释放锁的期间,之前在获取锁的时候它拷贝了锁对象头的Mark Word,在释放锁的时候如果它发现在它持有锁的期间有其他线程来尝试获取锁了,并且该线程对Mark Word做了修改,两者比对发现不一致,则切换到重量锁。因为重量级锁被修改了,所以display mark word和原来的Mark Word不一样了。


怎么补救,就是进入mutex前,compare一下obj的Mark Word状态。确认该Mark Word是否被其他线程持有。此时如果线程已经释放了Mark Word,那么通过CAS后就可以直接进入线程,无需进入mutex,就这个作用。


尝试获取锁线程视角(类比线程二):如果线程尝试获取锁的时候,轻量锁正被其他线程占有,那么它就会修改Mark Word,修改重量级锁,表示该进入重量锁了。


从 JDK1.7 开始,自旋锁默认启用,自旋次数由 JVM 设置决定,这里不建议大家设置的重试次数过多,因为 CAS 重试操作意味着长时间地占用 CPU。自旋锁重试之后如果抢锁依然失败,同步锁就会升级至重量级锁,锁标志位改为 10。在这个状态下,未抢到锁的线程都会进入 Monitor,之后会被阻塞在 _WaitSet 队列中。


3.3 重量级锁

重量级锁时Mark Word字段结构图(32位操作系统):


image.png


前面我们提到的Mark Word,若是重量锁,对象头中还会存在一个监视器对象,也就是Monitor对象。这个Monitor对象就是实现synchronized的一个关键。


在Java虚拟机(HotSpot)中,Monitor对象其实就是ObjectMonitor对象,这个对象是一个C++对象,定义在虚拟机源码中。


ObjectMonitor有比较多的属性,但是比较重要的属性有四个:


_count:计数器。用来记录获取锁的次数。该属性主要用来实现重入锁机制。

_owner:记录着当前锁对象的持有者线程。

_WaitSet:队列。当一个线程调用了wait方法后,它会释放锁资源,进入WaitSet队列等待被唤醒。

_EntryList:队列。里面存放着所有申请该锁对象的线程。

所以一个线程获取锁对象的流程如下:


判断锁对象的锁标志位是重量级锁,于是想要获取Monitor对象锁。

如果Monitor中的_count属性是0,说明当前锁可用,于是把 _owner 属性设置为本线程,然后把 _count 属性+1。这就成功地完成了锁的获取。


如果Monitor中的_count属性不为0,再检查 _owner 属性,如果该属性指向了本线程,说明可以重入锁,于是把 _count 属性再加上1,实现锁的冲入。


如果 _owner 属性指向了其他线程,那么该线程进入 _EntryList 队列中等待锁资源的释放。


如果线程在持有锁的过程中调用了wait()方法,那么线程释放锁对象,然后进入 _WaitSet 队列中等待被唤醒。


3.4 总结

synchronized的执行过程:


  1. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁
  2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1
  3. 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。
  4. 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁
  5. 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
  6. 如果自旋成功则依然处于轻量级状态。
  7. 如果自旋失败,则升级为重量级锁。


上面几种锁都是JVM自己内部实现,当我们执行synchronized同步块的时候jvm会根据启用的锁和当前线程的争用情况,决定如何执行同步操作;


在所有的锁都启用的情况下线程进入临界区时会先去获取偏向锁,如果已经存在偏向锁了,则会尝试获取轻量级锁,启用自旋锁,如果自旋也没有获取到锁,则使用重量级锁,没有获取到锁的线程阻塞挂起,直到持有锁的线程执行完同步块唤醒他们;


synchronized锁升级实际上是把本来的悲观锁变成了在一定条件下使用无锁(同样线程获取相同资源的偏向锁),以及使用乐观(自旋锁 cas)和一定条件下悲观(重量级锁)的形式。


锁的优缺点对比:


image.png


3.5 锁膨胀升级一览图

图来自鲁班学院公开课资料中


image.png

目录
相关文章
|
6天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
20 2
|
11天前
|
存储 安全 Java
面试高频:Synchronized 原理,建议收藏备用 !
本文详解Synchronized原理,包括其作用、使用方式、底层实现及锁升级机制。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
面试高频:Synchronized 原理,建议收藏备用 !
|
6天前
|
存储 网络协议 安全
30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场
本文精选了 30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场。
19 2
|
17天前
|
存储 NoSQL MongoDB
MongoDB面试专题33道解析
大家好,我是 V 哥。今天为大家整理了 MongoDB 面试题,涵盖 NoSQL 数据库基础、MongoDB 的核心概念、集群与分片、备份恢复、性能优化等内容。这些题目和解答不仅适合面试准备,也是日常工作中深入理解 MongoDB 的宝贵资料。希望对大家有所帮助!
|
22天前
|
缓存 前端开发 JavaScript
"面试通关秘籍:深度解析浏览器面试必考问题,从重绘回流到事件委托,让你一举拿下前端 Offer!"
【10月更文挑战第23天】在前端开发面试中,浏览器相关知识是必考内容。本文总结了四个常见问题:浏览器渲染机制、重绘与回流、性能优化及事件委托。通过具体示例和对比分析,帮助求职者更好地理解和准备面试。掌握这些知识点,有助于提升面试表现和实际工作能力。
57 1
|
1月前
|
NoSQL Java API
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试一线互联网企业时遇到了关于Redis分布式锁过期及自动续期的问题。尼恩对此进行了系统化的梳理,介绍了两种核心解决方案:一是通过增加版本号实现乐观锁,二是利用watch dog自动续期机制。后者通过后台线程定期检查锁的状态并在必要时延长锁的过期时间,确保锁不会因超时而意外释放。尼恩还分享了详细的代码实现和原理分析,帮助读者深入理解并掌握这些技术点,以便在面试中自信应对相关问题。更多技术细节和面试准备资料可在尼恩的技术文章和《尼恩Java面试宝典》中获取。
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
|
1月前
|
存储 安全 Java
JVM锁的膨胀过程与锁内存变化解析
在Java虚拟机(JVM)中,锁机制是确保多线程环境下数据一致性和线程安全的重要手段。随着线程对共享资源的竞争程度不同,JVM中的锁会经历从低级到高级的膨胀过程,以适应不同的并发场景。本文将深入探讨JVM锁的膨胀过程,以及锁在内存中的变化。
41 1
|
1月前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。
|
2月前
|
缓存 Android开发 开发者
Android RecycleView 深度解析与面试题梳理
本文详细介绍了Android开发中高效且功能强大的`RecyclerView`,包括其架构概览、工作流程及滑动优化机制,并解析了常见的面试题。通过理解`RecyclerView`的核心组件及其优化技巧,帮助开发者提升应用性能并应对技术面试。
89 8
|
2月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
67 8

推荐镜像

更多