面试题答案
一键面试Java中的锁机制
- synchronized:
- 原理:是Java的内置关键字,基于JVM实现。当一个线程访问同步代码块时,它首先获取对象的监视器(monitor),其他线程若想进入该同步代码块,必须等待前一个线程释放监视器。
- 特性:可修饰方法(实例方法、静态方法)和代码块。修饰实例方法时,锁对象是当前实例;修饰静态方法时,锁对象是类的Class对象;修饰代码块时,锁对象是指定的对象。
- 缺点:是一种重量级锁,获取和释放锁开销较大,在高竞争场景下性能较差。
- ReentrantLock:
- 原理:基于AQS(AbstractQueuedSynchronizer)框架实现。线程获取锁时,会尝试修改AQS的状态变量,如果成功则获取锁,否则进入等待队列。
- 特性:支持公平锁和非公平锁(默认非公平),公平锁按线程等待顺序获取锁,非公平锁可抢占锁,性能通常比公平锁好;支持锁中断,可中断等待锁的线程;可实现读写锁(ReadWriteLock),允许多个线程同时读,但写操作需独占锁。
锁的优化技术
- 锁粗化:
- 原理:将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。例如,在一个循环中多次对同一对象加锁解锁,JVM会将这些操作合并为一次加锁解锁。
- 适用场景:适用于循环内多次对同一对象加锁的场景,减少频繁加锁解锁的开销。
- 潜在风险:锁粗化可能导致锁的持有时间变长,影响其他线程对该锁的竞争,在高并发场景下可能降低系统的并发度。
- 锁消除:
- 原理:JVM在编译期,通过逃逸分析,判断某段代码中,对象的锁操作是否永远不会被其他线程访问到,如果是,则消除该锁操作。例如,一个方法中创建的对象只在该方法内部使用,对该对象的锁操作可被消除。
- 适用场景:适用于对象只在方法内部使用且不存在竞争的场景。
- 潜在风险:逃逸分析技术本身有一定局限性,可能误判,导致不应消除的锁被消除,影响程序的线程安全性。
- 偏向锁:
- 原理:当一个线程访问同步块并获取锁时,会在对象头中记录该线程ID,以后该线程再次访问该同步块时,无需再次获取锁,直接进入同步块。只有当有其他线程尝试获取锁时,偏向锁才会升级为轻量级锁。
- 适用场景:适用于只有一个线程频繁访问同步块的场景,可减少锁获取的开销。
- 潜在风险:若实际应用场景并非单线程频繁访问,而是多线程交替访问,偏向锁的撤销和升级操作会带来额外开销,降低性能。
- 轻量级锁:
- 原理:当偏向锁升级时,线程在自己的栈帧中创建锁记录(Lock Record),将对象头中的Mark Word复制到锁记录中,然后尝试使用CAS操作将对象头的Mark Word替换为指向锁记录的指针。若成功则获取轻量级锁,失败则升级为重量级锁。
- 适用场景:适用于竞争不太激烈的场景,通过CAS操作避免重量级锁的系统调用开销。
- 潜在风险:若竞争激烈,CAS操作失败次数增多,导致锁频繁升级为重量级锁,反而增加性能开销。
不同竞争激烈程度场景下的锁优化策略
- 低竞争场景:
- 推荐策略:使用偏向锁或轻量级锁,利用它们在低竞争下的低开销优势。启用偏向锁可通过JVM参数
-XX:+UseBiasedLocking
开启。代码中可尽量将同步代码块内的操作优化,减少锁的持有时间。 - 示例:单线程处理任务的场景,如某些初始化操作,可使用偏向锁提高性能。
- 推荐策略:使用偏向锁或轻量级锁,利用它们在低竞争下的低开销优势。启用偏向锁可通过JVM参数
- 中竞争场景:
- 推荐策略:可考虑使用轻量级锁。同时,可结合锁粗化和锁消除技术,减少不必要的锁操作。例如,将循环内对同一对象的多次同步操作合并为一次(锁粗化),对局部对象的同步操作进行锁消除。
- 示例:多个线程交替访问同步资源,但竞争不太激烈,如简单的缓存读写操作。
- 高竞争场景:
- 推荐策略:使用ReentrantLock,并考虑使用非公平锁以提高吞吐量;还可通过读写锁(ReadWriteLock)来提高读操作的并发度。对于竞争激烈的资源,可尝试使用分段锁技术,将大的资源分成多个段,每个段使用独立的锁,降低锁竞争。
- 示例:高并发的数据库读写操作,使用读写锁可让读操作并行执行,提高性能。