关于锁

本文详细探讨了Java中的锁机制,包括CAS、自旋锁、锁升级、偏向锁、轻量级锁和重量级锁的概念及其工作原理。同时,文章对比了synchronized与Lock(CAS)在不同场景下的性能表现,介绍了锁消除、锁粗化等优化技术,以及锁降级的相关讨论。

注:摘抄自马士兵老师课堂笔记

CAS

Compare And Swap (Compare And Exchange) / 自旋 / 自旋锁 / 无锁

锁升级

无锁 -> 偏向锁 -> 轻量级锁 (自旋锁,自适应自旋)-> 重量级锁

轻量级锁

  默认情况 偏向锁有个时延,默认是4秒。
  因为JVM虚拟机自己有一些默认启动的线程,里面有好多sync代码,这些sync代码启动时就知道肯定会有竞争,如果使用偏向锁,就会造成偏向锁不断的进行锁撤销和锁升级的操作,效率较低。
  可以调整:

-XX:BiasedLockingStartupDelay=0

偏向锁 - markword 上记录当前线程指针,下次同一个线程加锁的时候,不需要争用,只需要判断线程指针是否同一个,所以,偏向锁,偏向加锁的第一个线程 。hashCode备份在线程栈上 线程销毁,锁降级为无锁

有争用 - 锁升级为轻量级锁 - 每个线程有自己的LockRecord在自己的线程栈上,用CAS去争用markword的LR的指针,指针指向哪个线程的LR,哪个线程就拥有锁

自旋超过10次,升级为重量级锁 - 如果太多线程自旋 CPU消耗过大,不如升级为重量级锁,进入等待队列(不消耗CPU)-XX:PreBlockSpin

自旋锁在 JDK1.4.2 中引入,使用 -XX:+UseSpinning 来开启。JDK 6 中变为默认开启,并且引入了自适应的自旋锁(适应性自旋锁)。

自适应自旋锁意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

偏向锁由于有锁撤销的过程revoke,会消耗系统资源,所以,在锁争用特别激烈的时候,用偏向锁未必效率高。还不如直接使用轻量级锁。

synchronized 和 Lock (CAS)比较

 在高争用 高耗时的环境下synchronized效率更高
 在低争用 低耗时的环境下CAS效率更高
 synchronized到重量级之后是等待队列(不消耗CPU)
 CAS(等待期间消耗CPU)
 
 一切以实测为准

锁消除 lock eliminate

public void add(String str1,String str2){
         StringBuffer sb = new StringBuffer();
         sb.append(str1).append(str2);
}

  我们都知道 StringBuffer 是线程安全的,因为它的关键方法都是被 synchronized 修饰过的,但我们看上面这段代码,我们会发现,sb 这个引用只会在 add 方法中使用,不可能被其它线程引用(因为是局部变量,栈私有),因此 sb 是不可能共享的资源,JVM 会自动消除 StringBuffer 对象内部的锁。

锁粗化 lock coarsening

public String test(String str){
       
       int i = 0;
       StringBuffer sb = new StringBuffer():
       while(i < 100){
           sb.append(str);
           i++;
       }
       return sb.toString():
}

&emps;&emps;JVM 会检测到这样一连串的操作都对同一个对象加锁(while 循环内 100 次执行 append,没有锁粗化的就要进行 100 次加锁/解锁),此时 JVM 就会将加锁的范围粗化到这一连串的操作的外部(比如 while 虚幻体外),使得这一连串操作只需要加一次锁即可。

锁降级(不重要)

https://www.zhihu.com/question/63859501

其实,只被VMThread访问,降级也就没啥意义了。所以可以简单认为锁降级不存在!

超线程

一个ALU + 两组Registers + PC

参考资料

http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

<think>嗯,我现在得准备一个关于Spring Boot中的面试问题介绍。首先,我需要回忆一下在Spring Boot中常用的机制,以及相关的面试问题。可能包括数据库、分布式、乐观和悲观这些概念吧。 首先,乐观和悲观是常见的。乐观通常用版本号或者时间戳来实现,比如JPA中的@Version注解。我记得Hibernate支持这个,可能面试会问到如何在Spring Data JPA中实现乐观。需要举一个例子,比如实体类里添加version字段,并用@Version标注,这样在更新时会检查版本号是否一致,不一致就抛OptimisticLockException。 然后是悲观,比如在数据库中使用SELECT FOR UPDATE,这在Spring Boot中可以通过@Lock注解来实现,设置LockModeType为PESSIMISTIC_WRITE。可能需要解释一下这个注解的作用和底层是怎么实现的,比如开启事务,执行select for update语句,定记录直到事务结束。 接下来是分布式,这在微服务架构中很重要。常见的实现方式有Redis的RedLock算法,或者使用Redisson客户端。还有基于ZooKeeper的临时顺序节点。在Spring Boot中整合Redisson的话,可能需要配置RedissonClient,然后使用RLock对象来加释放。例如,用tryLock方法设置超时时间,避免死。另外,Spring Integration也提供了分布式的抽象,比如使用Redis或者JDBC实现的,可能这也是一个考点。 另外,面试可能会问及数据库隔离级别和的关系。比如读未提交、读已提交、可重复读、序列化这些隔离级别,分别对应不同的机制。比如可重复读通常使用行,而序列化可能会用范围。需要结合Spring中的@Transactional注解的isolation属性来说明如何设置隔离级别,以及不同级别下的行为。 还有可能涉及到死的问题,如何避免死。比如在应用中按照固定顺序获取,设置超时时间,使用数据库的死检测机制等。在Spring Boot中,处理死可能需要结合事务管理和重试机制,比如用@Retryable注解在发生死异常时重试操作。 另外,面试问题可能会比较实际,比如在高并发场景下如何保证数据一致性,这时候需要根据情况选择乐观、悲观或分布式。比如库存扣减,如果是单机可以用同步或悲观,分布式环境下得用Redis或ZooKeeper实现的分布式。 还有需要注意的地方,比如的粒度。太细的管理复杂,太粗的会影响并发性能。比如在更新用户余额时,住用户的行记录,而不是整个表。在Spring Boot中,结合JPA或MyBatis如何实现行级。 另外,Spring的声明式事务管理@Transactional和编程式事务TransactionTemplate在使用的时候有什么区别。比如,声明式事务可能更方便,但编程式事务可以更灵活地控制事务边界和的持有时间。 可能还需要比较不同的分布式方案优缺点,比如Redis的分布式实现简单性能好,但可能出现过期而业务未执行完的问题;ZooKeeper的通过临时节点实现,可靠性高,但性能相对较低。在Spring Cloud中,可以结合Spring Cloud Stream或者Spring Cloud Zookeeper来实现。 还有,Spring Boot中如何测试机制,比如使用@Test方法,启动嵌入式数据库或者Mock相关组件,测试并发情况下的是否有效。可能需要用到多线程测试,或者集成测试框架。 总结一下,面试问题可能围绕以下几个方面: 1. 乐观和悲观的实现及区别。 2. 分布式的实现方式及其在Spring Boot中的应用。 3. 数据库隔离级别与的关系。 4. 死的预防和处理。 5. 的粒度选择及性能影响。 6. 实际应用场景下的选择策略。 7. 测试机制的方法。 在回答时,需要结合Spring Boot的具体技术栈,比如JPA、MyBatis、Redisson、Spring Integration等,给出具体的示例和配置方法。同时,要注意解释清楚每种的适用场景和潜在问题,展示对并发控制和数据一致性的理解。</think>在Spring Boot面试中,关于的问题通常涉及并发控制、分布式场景及数据库事务管理。以下是关键知识点及示例: --- ### **一、乐观与悲观** #### 1. **乐观** - **实现原理**:通过版本号(或时间戳)实现,更新时检查版本是否一致。 - **Spring Boot应用**: 在JPA实体类中添加`@Version`字段: ```java @Entity public class Product { @Id private Long id; private String name; @Version private Integer version; // getters/setters } ``` 更新时若版本冲突,会抛出`OptimisticLockException`。 #### 2. **悲观** - **实现原理**:直接定数据库记录(如`SELECT ... FOR UPDATE`)。 - **Spring Boot应用**: 使用`@Lock(LockModeType.PESSIMISTIC_WRITE)`注解: ```java @Repository public interface ProductRepository extends JpaRepository<Product, Long> { @Lock(LockModeType.PESSIMISTIC_WRITE) Product findById(Long id); } ``` 事务提交前,其他线程无法修改该记录。 --- ### **二、分布式** #### 1. **基于Redis** - **实现工具**:Redisson或Spring Integration。 - **示例(Redisson)**: ```java @Autowired private RedissonClient redisson; public void updateStock(String productId) { RLock lock = redisson.getLock("product_lock:" + productId); try { if (lock.tryLock(10, 30, TimeUnit.SECONDS)) { // 执行业务逻辑 } } finally { lock.unlock(); } } ``` #### 2. **基于ZooKeeper** - **原理**:利用临时顺序节点实现,Spring Cloud Zookeeper提供集成支持。 --- ### **三、数据库隔离级别与** - **隔离级别**:通过`@Transactional(isolation = Isolation.READ_COMMITTED)`设置。 - **行为**: - `READ_COMMITTED`:行级,避免脏读。 - `SERIALIZABLE`:范围,避免幻读。 --- ### **四、死处理** - **预防策略**: 1. 按固定顺序获取。 2. 设置超时(如Redis的`tryLock(10, TimeUnit.SECONDS)`)。 - **Spring重试机制**: ```java @Retryable(value = CannotAcquireLockException.class, maxAttempts = 3) public void deductStock() { // 业务代码 } ``` --- ### **五、实际场景选择的准则** 1. **单机低并发**:优先用JPA乐观或数据库悲观。 2. **分布式高并发**:选择Redis/ZooKeeper分布式。 3. **读多写少**:乐观性能更优。 4. **写多冲突频繁**:悲观减少重试开销。 --- ### **六、高频面试问题** 1. **乐观和悲观的区别及适用场景?** - 答:乐观通过版本号控制,适合读多写少;悲观直接加,适合写多冲突高。 2. **如何用Redisson实现分布式?** - 答:通过`RLock.tryLock()`非阻塞尝试加,设置超时避免死。 3. **@Transactional和的关系?** - 答:事务注解管理数据库连接和隔离级别,的生效依赖事务边界(如方法执行完毕提交后释放)。 4. **如何避免库存超卖?** - 答:结合数据库行(`SELECT FOR UPDATE`)或Redis分布式,保证扣减操作的原子性。 --- 通过理解上述核心点,结合代码示例和场景分析,可以系统回答Spring Boot中相关的面试问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值