2024年10月19日 虾皮购物 一面 已挂
以下是整理后的 Markdown 格式内容,优化了标题层级、列表嵌套和代码块显示,提升可读性:
1. Java中的锁机制 - 可重入锁
锁机制
- 同步方法和同步块
- Lock接口
可重入锁(Reentrant Lock)
定义:允许同一线程多次获取同一把锁,避免递归调用或多层方法调用时的死锁问题。
特点:
- 重入性
- 公平性策略
- 手动锁管理
示例代码:
import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockExample { private final ReentrantLock lock = new ReentrantLock(); public void methodA() { lock.lock(); // 获取锁 try { System.out.println("In method A"); methodB(); // 同一线程可重入调用 } finally { lock.unlock(); // 释放锁 } } public void methodB() { lock.lock(); // 再次获取锁 try { System.out.println("In method B"); } finally { lock.unlock(); } } public static void main(String[] args) { new ReentrantLockExample().methodA(); } }
总结:可重入锁通过计数器实现重入逻辑,适用于递归调用、复杂同步场景,需注意手动释放锁以避免资源泄漏。
2. 可重入锁的使用场景
- 递归调用
import java.util.concurrent.locks.ReentrantLock; public class RecursiveLockExample { private final ReentrantLock lock = new ReentrantLock(); private int counter = 0; public void increment() { lock.lock(); try { counter++; System.out.println("Counter: " + counter); if (counter < 5) increment(); // 递归调用 } finally { lock.unlock(); } } public static void main(String[] args) { new RecursiveLockExample().increment(); } }
- 多线程资源共享
import java.util.concurrent.locks.ReentrantLock; public class SharedResource { private final ReentrantLock lock = new ReentrantLock(); private int count = 0; public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } // 省略getCount和多线程测试代码 }
- 复杂对象状态管理
public class ComplexObject { private final ReentrantLock lock = new ReentrantLock(); private String state = ""; public void updateState(String newState) { lock.lock(); try { state = newState; processState(); // 同一线程内调用其他方法 } finally { lock.unlock(); } } private void processState() { /* 处理状态 */ } }
- 保证顺序执行
public class SequentialExecution { private final ReentrantLock lock = new ReentrantLock(); private boolean initialized = false; public void performTask() { lock.lock(); try { if (!initialized) initialize(); // 确保先初始化 // 执行任务 } finally { lock.unlock(); } } }
总结:可重入锁在递归、资源竞争、状态管理和顺序控制场景中能有效避免死锁,提升并发安全性。
3. 讲一下AQS(AbstractQueuedSynchronizer)
AQS的基本概念
- 核心结构:
AQS的主要方法
方法名 | 作用描述 |
| 尝试获取独占锁,成功返回 |
| 尝试释放独占锁,成功返回 |
| 阻塞获取锁,调用 |
| 释放锁,调用 |
| 获取/设置同步状态 |
AQS的实现示例(独占锁)
import java.util.concurrent.locks.AbstractQueuedSynchronizer; public class MyLock { private static class Sync extends AbstractQueuedSynchronizer { // 尝试获取锁(CAS操作) protected boolean tryAcquire(int arg) { if (compareAndSetState(0, 1)) { // 状态0→1表示获取锁 setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } // 尝试释放锁 protected boolean tryRelease(int arg) { if (getState() == 0) throw new IllegalMonitorStateException(); setExclusiveOwnerThread(null); setState(0); return true; } // 判断当前线程是否持有锁 protected boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } } private final Sync sync = new Sync(); public void lock() { sync.acquire(1); } public void unlock() { sync.release(1); } }
使用AQS的场景
- JUC组件底层实现:
AQS的优势
- 高性能:通过CAS和队列减少线程阻塞,适用于高并发场景。
- 灵活性:支持自定义同步逻辑(独占/共享模式)。
- 可扩展:通过继承AQS并实现抽象方法,快速实现自定义同步器。
总结:AQS是Java并发包的核心框架,通过队列和状态管理实现高效同步,是理解ReentrantLock
等工具的基础。
4. Redis的相关数据结构
1. 字符串(String)
- 底层结构:
2. 哈希(Hash)
- 底层结构:
3. 列表(List)
- 底层结构:
4. 集合(Set)
- 底层结构:
5. 有序集合(Sorted Set)
- 底层结构:
5. 为什么每种数据类型一般有两种数据结构?
设计思想:空间与时间的权衡,根据数据量动态切换结构以优化性能。
- 小数据量场景:使用紧凑结构(如压缩列表),减少内存占用。
- 大数据量场景:切换为普通结构(如哈希表、跳表),提升操作效率。
优势:在内存占用和操作性能间取得平衡,适应不同业务场景。
6. JVM相关内存结构
1. 方法区(Method Area)
- 作用:存储类元数据(类名、字段、方法、常量池、静态变量等)。
- 演进:
2. 堆(Heap)
- 作用:存储对象实例,是GC的主要管理区域。
- 分区:
3. 栈(Java Virtual Machine Stack)
- 作用:每个线程独有,存储局部变量、方法调用栈帧(包括操作数栈、动态链接、返回地址等)。
- 特点:线程私有,后进先出(LIFO),栈深度过大会导致
StackOverflowError
。
4. 程序计数器(Program Counter Register)
- 作用:记录当前线程执行的字节码指令地址,线程切换时用于恢复执行位置。
- 特点:线程私有,是JVM中唯一无内存溢出风险的区域。
5. 本地方法栈(Native Method Stack)
- 作用:存储本地方法(
native
修饰的方法)的调用栈帧,如调用C/C++代码时使用。
7. HashMap的底层原理
1. 数据结构
- 数组+链表/红黑树:
2. 哈希函数
- 扰动处理:对
hashCode()
返回值进行高位移异或((h = key.hashCode()) ^ (h >>> 16)
),降低哈希冲突概率。
3. 添加元素流程
- 计算键的哈希值,确定数组索引。
- 若桶为空,直接插入新节点。
- 若桶非空:
- 若链表长度≥8且数组容量≥64,链表转红黑树。
- 若元素个数超过
threshold(容量×负载因子,默认16×0.75=12)
,触发扩容。
4. 获取元素流程
- 计算键的哈希值,定位数组索引。
- 若桶为空,返回
null
; - 遍历链表/红黑树,匹配键相等的节点,返回对应值。
5. 扩容机制
- 触发条件:元素个数 >
threshold
。 - 扩容逻辑:
6. 线程安全
- 非线程安全:多线程并发修改可能导致链表成环、数据丢失等问题。
- 解决方案:
总结:HashMap通过哈希函数和动态数组实现高效存取,通过链表/红黑树处理冲突,适用于单线程高性能场景,多线程需额外同步机制。
#面试问题记录##牛客创作赏金赛#27双非 Java后端开发面经 文章被收录于专栏
来源于自己的记录但是并不都是自己的面试经历 所有解答均是主观看法一个字一个字的敲的...