java里面几种锁的区别。。

本文深入探讨了并发控制中的乐观锁与悲观锁概念,解释了它们的工作原理及应用场景。乐观锁通常用于读多写少的场景,通过版本号或CAS算法避免不必要的锁竞争,提高系统吞吐量;而悲观锁则假设最坏情况,适用于写操作频繁的场景,如数据库的锁机制。此外,文章还详细解析了Java中synchronized关键字的原理,包括轻量锁和偏向锁的优化策略。

乐观锁和悲观锁
 悲观锁: 假设在最坏的情况下,去拿数据的时候,认为别人会修改数据,所以在访问数据的时候,就要把锁加上,那么其他线程来获取这个数据的时候,先阻塞直到获得锁对象。传统关系型数据库就是利用这种锁机制来实现,读写操作,java里面可以利用synchronized 和ReenrantLock 来实现    
乐观锁: 在最后的情况下,每次访问数据的时候不会有其他线程来修改这个数据,不用加锁。但是在更新这个数据的时候会去判断有没有线程更新过,利用CAS(compare and swap)算法来实现。
    java.util.concurrent包下面的原子类利用了CAS实现。
    同时乐观锁多应用与读的场景,这样可以很好的提高吞吐量
    乐观锁的两种实现方式 :
    版本号   一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。
    当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,
    若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
    CAS算法  比较与交换  无锁编程,在不使用锁的情况下实现多线程之间的同步问题。
    CAS算法设计的三个值:需要读写的内存值V 进行比较的值 A  拟写入的新值 B
 当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
  CAS带来的问题:
       ABA问题  
     1、可以发现,CAS实现的过程是先取出内存中某时刻的数据,在下一时刻比较并替换,那么在这个时间差会导致数据的变化,此时就会导致出现“ABA”问题。 
     2、什么是”ABA”问题? 
     比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。 
  尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
    AtomicStampedReference 类来解决这个问题
     只能保证单个共享变量的原子操作
     自旋时间太长影响CPu执行效率 
  
   关于synchronized关键字原理:
  同步块。同步方法  同步静态方法(锁当前的Class对象)JVM里面用两个指令 monitorenter  moniterexit指令来分别表示进入 和退出锁 
  synchronized 锁开销比较大  为了进一步优化 引入了轻量锁 偏向锁
   轻量锁  
   当线程进入到同步块时,如果同步对象为无锁的时候,当前线程会在栈帧中创建一个锁记录区域,把锁对象的Mark word的数据拷贝到这个区域,在利用CAS算法来更新记录。如果更新成功就表示获得锁对象了。否则检查当前记录区域的数据示指向当前的锁对象吗?如果是则直接进入,说明当前线程拥有该锁对象的锁,否则不是,存在多个线程同时竞争一把锁,则轻量锁升级为重量锁。
      
    偏向锁 的特征,不存在多线程竞争,应该让一个线程多次获得锁
    当线程访问同步代码块的时候,则利用CAS那线程的ID更新到锁对象的MArk word,如果更新成功,则表示获取锁成功,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。
 那么其他线程想要获取偏向锁,怎么办?
     当其他线程想要获取偏向锁的时候,持有偏向锁的线程就会释放锁,释放时会等待全局安全点(这一时刻没有字节码运行),
     接着会暂停拥有偏向锁的线程,根据锁对象目前是否被锁来判定将对象头中的 Mark Word 设置为无锁或者是轻量锁状态。乐观锁和悲观锁
 悲观锁: 假设在最坏的情况下,去拿数据的时候,认为别人会修改数据,所以在访问数据的时候,就要把锁加上,那么其他线程来获取这个数据的时候,先阻塞直到获得锁对象。传统关系型数据库就是利用这种锁机制来实现,读写操作,java里面可以利用synchronized 和ReenrantLock 来实现    
乐观锁: 在最后的情况下,每次访问数据的时候不会有其他线程来修改这个数据,不用加锁。但是在更新这个数据的时候会去判断有没有线程更新过,利用CAS(compare and swap)算法来实现。
    java.util.concurrent包下面的原子类利用了CAS实现。
    同时乐观锁多应用与读的场景,这样可以很好的提高吞吐量
    乐观锁的两种实现方式 :
    版本号   一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。
    当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,
    若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
    CAS算法  比较与交换  无锁编程,在不使用锁的情况下实现多线程之间的同步问题。
    CAS算法设计的三个值:需要读写的内存值V 进行比较的值 A  拟写入的新值 B
 当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
  CAS带来的问题:
       ABA问题  
     1、可以发现,CAS实现的过程是先取出内存中某时刻的数据,在下一时刻比较并替换,那么在这个时间差会导致数据的变化,此时就会导致出现“ABA”问题。 
     2、什么是”ABA”问题? 
     比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。 
  尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
    AtomicStampedReference 类来解决这个问题
     只能保证单个共享变量的原子操作
     自旋时间太长影响CPu执行效率 
  
   关于synchronized关键字原理:
  同步块。同步方法  同步静态方法(锁当前的Class对象)JVM里面用两个指令 monitorenter  moniterexit指令来分别表示进入 和退出锁 
  synchronized 锁开销比较大  为了进一步优化 引入了轻量锁 偏向锁
   轻量锁  
   当线程进入到同步块时,如果同步对象为无锁的时候,当前线程会在栈帧中创建一个锁记录区域,把锁对象的Mark word的数据拷贝到这个区域,在利用CAS算法来更新记录。如果更新成功就表示获得锁对象了。否则检查当前记录区域的数据示指向当前的锁对象吗?如果是则直接进入,说明当前线程拥有该锁对象的锁,否则不是,存在多个线程同时竞争一把锁,则轻量锁升级为重量锁。
      
    偏向锁 的特征,不存在多线程竞争,应该让一个线程多次获得锁
    当线程访问同步代码块的时候,则利用CAS那线程的ID更新到锁对象的MArk word,如果更新成功,则表示获取锁成功,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。
 那么其他线程想要获取偏向锁,怎么办?
     当其他线程想要获取偏向锁的时候,持有偏向锁的线程就会释放锁,释放时会等待全局安全点(这一时刻没有字节码运行),
     接着会暂停拥有偏向锁的线程,根据锁对象目前是否被锁来判定将对象头中的 Mark Word 设置为无锁或者是轻量锁状态。

<think>好的,用户想了解乐观锁和悲观区别及使用场景。首先,我需要回忆这两个概念的定义。悲观应该是假设会发生并发冲突,所以每次访问数据都会加,防止别人修改。而乐观锁则相反,假设冲突很少发生,只在更新时检查数据是否被修改过。接下来,要比较它们的区别。可能需要从加时机、实现方式、适用场景等方面展开。比如,悲观在操作前加,如synchronized或ReentrantLock;乐观锁用版本号或CAS机制,比如数据库的version字段或Atomic类。 然后,使用场景方面,悲观适合写操作多的场景,能避免频繁冲突导致的回滚。乐观锁适合读多写少,减少加的开销,提高吞吐量。需要引用用户提供的资料,比如引用3提到读取频繁用乐观,写入频繁用悲观。还要注意是否需要提到具体例子,比如数据库中的行和MVCC,或者Java中的原子类。可能用户是开发者,需要实际应用的例子,所以代码示例会有帮助,比如CAS的compareAndSet操作。另外,用户可能关心性能影响,比如悲观可能导致线程阻塞,影响性能;而乐观锁在高冲突时重试次数多,反而降低效率。需要总结两者的优缺点,帮助用户根据场景选择。 最后,生成相关问题,比如如何实现乐观锁,分布式区别,或者性能优化方法。这些问题能帮助用户深入理解主题。</think>### 乐观锁与悲观区别及使用场景 #### 一、核心区别 1. **加时机** - **悲观**:假设数据一定会发生冲突,在操作前**先加**(如`synchronized`、`SELECT ... FOR UPDATE`),确保独占资源[^2][^3]。 - **乐观锁**:假设冲突概率低,仅在提交更新时**验证数据一致性**(如版本号、CAS机制),若冲突则重试或放弃[^1][^4]。 2. **实现方式** - **悲观**通过**阻塞其他线程**实现,例如: ```java synchronized (lock) { /* 操作共享资源 */ } ``` - **乐观锁**通过**非阻塞算法**实现,例如数据库的`version`字段或原子类: ```sql UPDATE table SET value = new_value, version = version + 1 WHERE id = 1 AND version = old_version; ``` ```java AtomicInteger atomicInt = new AtomicInteger(0); atomicInt.compareAndSet(0, 1); // CAS操作 ``` 3. **性能影响** - **悲观**:线程阻塞可能导致上下文切换开销,**适合高冲突场景**(如频繁修改订单状态)[^3]。 - **乐观锁**:无竞争,但高冲突时频繁重试会降低吞吐量,**适合读多写少场景**(如商品库存查询)[^1][^4]。 #### 二、使用场景对比 | 维度 | 悲观 | 乐观锁 | |--------------|--------------------------------|--------------------------------| | **适用场景** | 写操作频繁(如银行转账) | 读操作频繁(如统计页面访问量) | | **冲突处理** | 直接阻塞其他线程 | 通过重试或业务逻辑处理冲突 | | **性能特点** | 高冲突下稳定,低冲突时性能差 | 低冲突时性能高,高冲突时性能差 | | **实现复杂度**| 简单(直接加) | 复杂(需处理版本控制或CAS) | #### 三、典型案例 1. **悲观** - **数据库行**:`SELECT ... FOR UPDATE`定订单记录,防止超卖[^3]。 - **Java同步代码块**:多线程环境下保证账户余额更新的原子性。 2. **乐观锁** - **版本号机制**:电商库存更新时校验版本号,避免超扣[^4]。 - **Redis分布式**:结合`WATCH`命令实现乐观锁,用于秒杀场景[^4]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值