linux中乐观锁和悲观锁_排他锁和共享锁 乐观锁 悲观锁 - CSDN
  • 乐观锁对应于生活乐观的人总是想着事情往好的方向发展,悲观锁对应于生活悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。 悲观锁 总是假设最坏的情况...

    推荐阅读:

    如何在技术领域持续成长

    后端程序员必备的Linux基础知识

    后端必备——数据通信知识(RPC、消息队列)一站式总结

    何谓悲观锁与乐观锁

    乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。

    悲观锁

    总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronizedReentrantLock等独占锁就是悲观锁思想的实现。

    乐观锁

    总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

    两种锁的使用场景

    从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

    乐观锁常见的两种实现方式

    乐观锁一般会使用版本号机制或CAS算法实现。

    1. 版本号机制

    一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

    举一个简单的例子:

    假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。当需要对账户信息表进行更新的时候,需要首先读取version字段。

    1. 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
    2. 在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。
    3. 操作员 A 完成了修改工作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,一致的话,就会将数据版本号加1( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
    4. 操作员 B 完成了操作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,而自己读取到的版本号为1 ,不满足 “ 当前最后更新的version与操作员第一次读取的版本号相等 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。

    这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。

    2. CAS算法

    compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数

    • 需要读写的内存值 V
    • 进行比较的值 A
    • 拟写入的新值 B

    当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试

    乐观锁的缺点

    ABA 问题是乐观锁一个常见的问题

    1 ABA 问题

    如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。

    JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

    2 循环时间长开销大

    自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

    3 只能保证一个共享变量的原子操作

    CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。

    CAS与synchronized的使用情景

    简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)

    1. 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
    2. 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

    补充: Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 “重量级锁” 。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 偏向锁轻量级锁 以及其它各种优化之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞竞争切换后继续竞争锁稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

    展开全文
  • 死锁概念:死锁产生的必要条件:死锁的预防死锁的避免 概念: 多个执行流对锁资源争抢访问,但是因为...请求与保持条件:我加了A,然后去请求B;如果不能对B加锁,则也不释放A 环路等待条件:我加了A,然


    概念

    乐观锁和悲观锁是两种思想,用于解决并发场景下的数据竞争问题。

    乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。

    悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。

    并发控制

    当程序中可能出现并发的情况时,我们就需要通过一定的手段来保证在并发情况下数据的准确性,通过这种手段保证了当用户和其他用户一起操作时,所得到的结果和他单独操作时的祷告的结果是一样的。

    这种手段就叫做并发控制。并发控制的目的是保证一个用户的工作不会对另一个用户的工作产生不合理的影响。

    没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。
    在这里插入图片描述

    我们常说的并发控制,一般都和数据库管理系统(DBMS)有关。在DBMS中的并发控制的任务,是确保在多个事务同时存取数据库中同一数据时,不破坏事务的隔离性和统一性以及数据库的统一性。

    实现并发控制的主要手段大致可以分为乐观并发控制和悲观并发控制两种。

    首先要明确:无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。其实不仅仅是关系型数据库系统中有乐观锁和悲观锁的概念,像hibernate、tair、memcache等都有类似的概念。所以,不应该拿乐观锁、悲观锁和其他的数据库锁等进行对比。

    悲观锁(Pessimistic Lock)

    我们要对一个数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。

    这种借助数据库锁机制在修改数据之前先锁定,再修改的方式被称之为悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)。

    之所以叫做悲观锁,是因为这是一种对数据的修改抱有悲观态度的并发控制方式。我们一般认为数据被并发修改的概率比较大,所以需要在修改之前先加锁。

    悲观锁主要分为共享锁或排他锁

    • 共享锁【Shared lock】又称为读锁,简称S锁。顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
    • 排他锁【Exclusive lock】又称为写锁,简称X锁。顾名思义,排他锁就是不能与其他锁并存,如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据行读取和修改。

    悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。
    在这里插入图片描述

    但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;
    另外,还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。

    悲观锁实现

    1. 在对记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。
    2. 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。具体响应方式由开发者根据实际需要决定。
    3. 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。
    4. 期间如果有其他对该记录做修改或加排他锁的操作,都会等待解锁或直接抛出异常。

    乐观锁( Optimistic Locking )

    乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

    相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。

    在这里插入图片描述

    乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。

    乐观锁实现

    乐观锁的概念中其实已经阐述了他的具体实现细节:主要就是两个步骤:冲突检测和数据更新。其实现方式有一种比较典型的就是Compare and Swap(CAS)。

    CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

    注意:乐观锁本身是不加锁的,只是在更新时判断一下数据是否被其他线程更新了

    CAS有哪些缺点?

    1. ABA问题

    假设有两个线程——线程1和线程2,两个线程按照顺序进行以下操作:

    • 线程1读取内存中数据为A;
    • 线程2将该数据修改为B;
    • 线程2将该数据修改为A;
    • 线程1对数据进行CAS操作

    在第(4)步中,由于内存中数据仍然为A,因此CAS操作成功,但实际上该数据已经被线程2修改过了。这就是ABA问题。

    ABA看起来似乎没有什么危害。但是在某些场景下,ABA却会带来隐患,例如栈顶问题:一个栈的栈顶经过两次(或多次)变化又恢复了原值,但是栈可能已发生了变化。

    对于ABA问题,比较有效的方案是引入版本号,内存中的值每发生一次变化,版本号都+1;在进行CAS操作时,不仅比较内存中的值,也会比较版本号,只有当二者都没有变化时,CAS才能执行成功。
    Java中的AtomicStampedReference类便是使用版本号来解决ABA问题的。

    1. 高竞争下的开销问题

    在并发冲突概率大的高竞争环境下,如果CAS一直失败,会一直重试,CPU开销较大。
    针对这个问题的一个思路是引入退出机制,如重试次数超过一定阈值后失败退出。当然,更重要的是避免在高竞争环境下使用乐观锁。

    1. 功能限制

    CAS的功能是比较受限的,例如CAS只能保证单个变量(或者说单个内存值)操作的原子性,这意味着:

    • 原子性不一定能保证线程安全,例如在Java中需要与volatile配合来保证线程安全
    • 当涉及到多个变量(内存值)时,CAS也无能为力。

    如何选择

    在乐观锁与悲观锁的选择上面,主要看下两者的区别以及适用场景就可以了。

    • 当竞争不激烈 (出现并发冲突的概率小)时,乐观锁更有优势,因为悲观锁会锁住代码块或数据,其他线程无法同时访问,影响并发,而且加锁和释放锁都需要消耗额外的资源。
    • 当竞争激烈(出现并发冲突的概率大)时,悲观锁更有优势,因为乐观锁在执行更新时频繁失败,需要不断重试,浪费CPU资源。

    随着互联网三高架构(高并发、高性能、高可用)的提出,悲观锁已经越来越少的被应用到生产环境中了,尤其是并发量比较大的业务场景。

    扩展:

    1. 阿里巴巴java开发手册:如果线程访问冲突小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不小于3次。

    如果本篇博文有帮助到你,欢迎留赞激励博主呀~~ 😁

    展开全文
  • 悲观锁 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享...JavasynchronizedReentrantLock等独占锁就是悲观锁思...

    悲观锁

    总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

    乐观锁

    总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法(一般为自旋锁,就是不断循环执行一段代码直到获取到锁)实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

    优缺点

    悲观锁:
    优点:在java1.6之后synchronized已经不是像以前那样‘重量级了’,在线程冲突较少的情况也能获得和CAS类似的性能,但在冲突较多的情况性能远高于乐观锁。
    乐观锁:
    优点:在冲突少的场景,可以省去很多锁的开销。
    缺点:1.如果循环(自旋)的时间长将会非常耗费cpu
    2.无法避免ABA问题,乐观锁在提交时会判断变量值有没有被改变,但是若值被改变后又被改回来,乐观锁就无法知道了。在java1.5后AtomicStampedReference类 提供了类似的功能
    3.只能保证一个共享变量的原子操作,CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。

    应用场景

    悲观锁:应用于多写的情况,一般会经常产生冲突,可以保证数据的一致性
    乐观锁:应用于多读的情况,可以减少一些锁的开销

    展开全文
  • 乐观锁和悲观锁 悲观锁:总是假设最坏的情况,每次获取数据时都会认为有其他线程对该数据进行修改,所以需要阻塞其他线程,直到自己释放锁。synchronized关键字就是典例。 悲观锁机制存在以下问题: 在多线程竞争...

    乐观锁和悲观锁

    悲观锁:总是假设最坏的情况,每次获取数据时都会认为有其他线程对该数据进行修改,所以需要阻塞其他线程,直到自己释放锁。synchronized关键字就是典例。

    悲观锁机制存在以下问题:

    1. 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
    2. 一个线程持有锁会导致其它所有需要此锁的线程挂起。
    3. 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

    乐观锁假设数据一般情况下不会产生并发冲突,再读取数据时不作操作,在更新数据的时候进行冲突并发检测,如果并发冲突了,则返回错误信息

    乐观锁实现:CAS

    需要三个数据:目标地址的值,目标地址期望值,自己对目标地址的更新值。

    拿到目标地址的值,和期望值做对比,如果相同,则说明没有线程修改目标地址的值,那么自己对目标地址的值进行更新。

    CAS实例:不加锁实现线程安全,针对单一共享变量
    public class AtomicInteger extends Number implements java.io.Serializable {  
        private volatile int value; 
    
        public final int get() {  
            return value;  
        }  
    
        public final int getAndIncrement() {  
            for (;;) {  
                int current = get();  
                int next = current + 1;  
                if (compareAndSet(current, next))  
                    return current;  
            }  
        }  
    
        public final boolean compareAndSet(int expect, int update) {  
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
        }  
    }
    

    get()方法得到volatile修饰的变量,作为expect值,使用next作为更新后的值,借用JNI完成CPU指令操作,CPU指令支持CAS操作。所以底层仍然依赖CPU。

    CAS缺点

    1. ABB问题

    因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
    从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

    1. 循环时间长,开销大
    2. 只能保证一个共享变量的原子操作

    解决:使用锁或者将多个变量合并成一个变量进行操作。或者使用AtomicReference类来保证引用对象的原子性。

    CAS与Synchronized的使用情景:

    1、对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

    2、对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

    补充: synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

    自旋锁

    定义

    当一个线程在获取到锁的时候,如果该锁已被其他线程获取,那么该线程将循环等待,然后不断地判断锁是否能成功获取,直到获取锁才退出循环。

    public class SpinLock {
        private AtomicReference<Thread> cas = new AtomicReference<Thread>();
        public void lock() {
            Thread current = Thread.currentThread();
            // 利用CAS
            while (!cas.compareAndSet(null, current)) {
                // DO nothing
                //像不像线程在旋转的感觉
            }
        }
        public void unlock() {
            Thread current = Thread.currentThread();
            cas.compareAndSet(current, null);
        }
    }
    

    compareAndSet如下:

    public final boolean compareAndSet(int expect, int update) {  
             return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
    }  
    

    而compareAndSwapInt类似下述逻辑:

    if (this == expect) {
        this = update
        return true;
    } else {
         return false;
    }
    

    cas.compareAndSet(null, current)在判断锁是否有线程在占用,如果有线程在占用,则返回false,那么进入while循环,开始自旋。
    unlock()方法类似。

    优点

    1. 自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快
    2. 非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。 (线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)

    自旋锁存在的问题

    1. 长时间未获取到锁,即长时间自旋,会消耗CPU资源,使用不当会造成CPU使用率极高。(线程如果无限循环,而且还占用CPU不干事儿,不是白吃饭不干活吗)
    2. 自旋锁不是公平的,无法满足等待时间最长的线程有限获取锁,可能存在线程饥饿问题。

    可重入自旋锁和不可重入自旋锁

    上述实现自旋锁的代码,观察lock()的while代码段,如果自己已经获取到该锁,就不可能再次获取到该锁。因为第二次获取到该锁时会进行是否有线程已获取到该锁的判断,该判断必定返回false,则必定进入while进行循环等待。即便第二次重新获取到该锁,在进行unlock()方法释放锁的时候也会将获取到的锁全部释放。

    为了实现可重入锁,需要引入锁的计数器

    public class ReentrantSpinLock {
        private AtomicReference<Thread> cas = new AtomicReference<Thread>();
        private int count;
        public void lock() {
            Thread current = Thread.currentThread();
            if (current == cas.get()) { // 如果当前线程已经获取到了锁,线程数增加一,然后返回
                count++;
                return;
            }
            // 如果没获取到锁,则通过CAS自旋
            while (!cas.compareAndSet(null, current)) {
                // DO nothing
            }
        }
        public void unlock() {
            Thread cur = Thread.currentThread();
            if (cur == cas.get()) {
                if (count > 0) {// 如果大于0,表示当前线程多次获取了该锁,释放锁通过count减一来模拟
                    count--;
                } else {// 如果count==0,可以将锁释放,这样就能保证获取锁的次数与释放锁的次数是一致的了。
                    cas.compareAndSet(cur, null);
                }
            }
        }
    }
    

    对比之前的不可重入锁的实现,主要改变如下:

    • 获取锁时,验证当前线程是否已经获取到该锁,如果已经获取到该锁,直接返回并且计数器加1,跳过自旋代码段。
    • 释放锁时,验证当前线程是否多次获取到该锁,只有当前线程仅获取到1次该锁时,才真正进行锁释放,否则技术器减1.

    总结:

    1. 悲观锁的缺点
    2. 乐观锁的实现CAS
    3. CAS和synchronized场景,即乐观锁和悲观锁使用的选择。
    4. 自旋锁与可重入自旋锁的实现。

    参考:

    Java并发问题–乐观锁与悲观锁以及乐观锁的一种实现方式-CAS

    面试必备之深入理解自旋锁

    展开全文
  • 乐观锁:每次去获取数据的时候都认为别人不会修改,不会上锁,但是在提交修改的时候会判断一下在此期间别人有没有修改这个数据。 悲观锁:每次去获取数据的时候都认为别人会修改,每次都会上锁,阻止其他线程获取...

    乐观锁:每次去获取数据的时候都认为别人不会修改,不会上锁,但是在提交修改的时候会判断一下在此期间别人有没有修改这个数据。
    悲观锁:每次去获取数据的时候都认为别人会修改,每次都会上锁,阻止其他线程获取数据,直到这个锁释放。

    MySQL 的乐观锁需要自己实现。一般在表里面添加一个 version 字段,每次修改成功值加 1;每次其他字段值的时候先对比一下,自己拥有的 version 和数据库现在的 version 是否一致,如果不一致就可以返回失败也可以进行重试。

    MySQL 的悲观锁,以 Innodb 存储引擎为例,将 MySQL 设置为非 autoCommit 模式

    begin;
    select * from table where id = 1 for update;
    insert ...
    update ...
    commit;

    当上面语句未 commit,id = 1 的数据是被锁定的,即其他事务中的查到这条语句会被阻塞,直到事务提交。

    数据的锁定还涉及到索引的不同可能是行锁、表锁的问题。

     

     


    【Java面试题与答案】整理推荐

     

    展开全文
  • 乐观锁对应于生活乐观的人总是想着事情往好的方向发展,悲观锁对应于生活悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。 悲观锁 总是假设最坏的情况,...
  • 最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁乐观锁 VS 悲观锁1.乐观锁2.悲观锁3.总之公平锁 VS 非公...
  • Linux中将在不同的角度进行了一些分类,这里记录一下Linux中提到的一些的概念以及其特点。 本文只对部分类型的的概念、特点进行记录,而不深究其实现。如果有什么理解有误的地方欢迎指正。 1、自旋...
  • 乐观锁/悲观锁 独享锁/共享锁 偏向锁/轻量级锁/重量级锁 分段锁 自旋锁 上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每个锁的名词进行一定的解释。 1、...
  • 最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁乐观锁 VS 悲观锁1.乐观锁2.悲观锁3.总之公平锁 VS 非公平锁1.公平锁2.非公平锁3.典型应用独享锁 VS 共享锁1.独享锁2.共享锁3.比较4.AQS分段锁Java线程...
  • 锁从宏观上分类,分为悲观锁乐观锁。 首先我们来看一下各个锁的解释: 假如你现在还在为自己的技术担忧,假如你现在想提升自己的工资,假如你想在职场上获得更多的话语权,假如你想顺利的度过35岁这个魔咒,假如...
  • 悲观锁事事皆总作最坏的打算,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让...
  • 悲观锁乐观锁 1、悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程...
  • 乐观锁悲观锁

    2014-12-06 20:23:51
    不知疲倦的猪 可以怀疑我的水平,...乐观锁悲观锁 文章转自网上好像是玉米田的,忘记了 锁( locking )  业务逻辑的实现过程,往往需要保证数据访问的排他性。如在金融系统的日终结算  处理,我们
1 2 3 4 5 ... 20
收藏数 6,690
精华内容 2,676
关键字:

linux中乐观锁和悲观锁