扫码关注公众号

前端操作系统之悲观锁与乐观锁
05-24
513观看
01

乐观锁一定就是好的吗?

乐观锁认为对一个对象的操作不会引发冲突,所以每次操作都不进行加锁,只是在最后提交更改时验证是否发生冲突,如果冲突则再试一遍,直至成功为止,这个尝试的过程称为自旋。乐观锁没有加锁,但乐观锁引入了ABA问题,此时一般采用版本号进行控制;也可能产生自旋次数过多问题,此时并不能提高效率,反而不如直接加锁的效率高;只能保证一个对象的原子性,可以封装成对象,再进行CAS操作;

来自:操作系统-锁-悲观锁、乐观锁
02

ReentrantLock 是如何实现可重入性的?

1.什么是可重入性一个线程持有锁时,当其他线程尝试获取该锁时,会被阻塞;而这个线程尝试获取自己持有锁时,如果成功说明该锁是可重入的,反之则不可重入。2.synchronized是如何实现可重入性synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令。每个锁对象内部维护一个计数器,该计数器初始值为0,表示任何线程都可以获取该锁并执行相应的方法。根据虚拟机规范要求,在执行monitorenter指令时,首先要尝试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了对象的锁,把锁的计数器+1,相应的在执行monitorexit指令后锁计数器-1,当计数器为0时,锁就被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。3.ReentrantLock如何实现可重入性ReentrantLock使用内部类Sync来管理锁,所以真正的获取锁是由Sync的实现类控制的。Sync有两个实现,分别为NonfairSync(非公公平锁)和FairSync(公平锁)。Sync通过继承AQS实现,在AQS中维护了一个privatevolatileintstate来计算重入次数,避免频繁的持有释放操作带来的线程问题。4.ReentrantLock代码实例//Sync继承于AQSabstractstaticclassSyncextendsAbstractQueuedSynchronizer{...}//ReentrantLock默认是非公平锁publicReentrantLock(){sync=newNonfairSync();}//可以通过向构造方法中传true来实现公平锁publicReentrantLock(booleanfair){sync=fair?newFairSync():newNonfairSync();}protectedfinalbooleantryAcquire(intacquires){//当前想要获取锁的线程finalThreadcurrent=Thread.currentThread();//当前锁的状态intc=getState();//state==0此时此刻没有线程持有锁if(c==0){//虽然此时此刻锁是可以用的,但是这是公平锁,既然是公平,就得讲究先来后到,//看看有没有别人在队列中等了半天了if(!hasQueuedPredecessors()&&//如果没有线程在等待,那就用CAS尝试一下,成功了就获取到锁了,//不成功的话,只能说明一个问题,就在刚刚几乎同一时刻有个线程抢先了=_=//因为刚刚还没人的,我判断过了compareAndSetState(0,acquires)){//到这里就是获取到锁了,标记一下,告诉大家,现在是我占用了锁setExclusiveOwnerThread(current);returntrue;}}//会进入这个elseif分支,说明是重入了,需要操作:state=state+1//这里不存在并发问题elseif(current==getExclusiveOwnerThread()){intnextc=c+acquires;if(nextc<0)thrownewError("Maximumlockcountexceeded");setState(nextc);returntrue;}//如果到这里,说明前面的if和elseif都没有返回true,说明没有获取到锁returnfalse;}5.代码分析当一个线程在获取锁过程中,先判断state的值是否为0,如果是表示没有线程持有锁,就可以尝试获取锁。当state的值不为0时,表示锁已经被一个线程占用了,这时会做一个判断current==getExclusiveOwnerThread(),这个方法返回的是当前持有锁的线程,这个判断是看当前持有锁的线程是不是自己,如果是自己,那么将state的值+1,表示重入返回即可。

来自:操作系统-锁-悲观锁、乐观锁
03

请你介绍一下,数据库乐观锁和悲观锁

悲观锁悲观锁(PessimisticLock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。Javasynchronized就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被block。乐观锁乐观锁(OptimisticLock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。乐观锁一般来说有以下2种方式:使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的“version”字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp),和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

来自:操作系统-锁-悲观锁、乐观锁
04

请说明一下锁机制的作用是什么?并且简述一下Hibernate的悲观锁和乐观锁机制是什么?

有些业务逻辑在执行过程中要求对数据进行排他性的访问,于是需要通过一些机制保证在此过程中数据被锁住不会被外界修改,这就是所谓的锁机制。Hibernate支持悲观锁和乐观锁两种锁机制。悲观锁,顾名思义悲观的认为在数据处理过程中极有可能存在修改数据的并发事务(包括本系统的其他事务或来自外部系统的事务),于是将处理的数据设置为锁定状态。悲观锁必须依赖数据库本身的锁机制才能真正保证数据访问的排他性,乐观锁,顾名思义,对并发事务持乐观态度(认为对数据的并发操作不会经常性的发生),通过更加宽松的锁机制来解决由于悲观锁排他性的数据访问对系统性能造成的严重影响。最常见的乐观锁是通过数据版本标识来实现的,读取数据时获得数据的版本号,更新数据时将此版本号加1,然后和数据库表对应记录的当前版本号进行比较,如果提交的数据版本号大于数据库中此记录的当前版本号则更新数据,否则认为是过期数据无法更新。Hibernate中通过Session的get()和load()方法从数据库中加载对象时可以通过参数指定使用悲观锁;而乐观锁可以通过给实体类加整型的版本字段再通过XML或@Version注解进行配置。使用乐观锁会增加了一个版本字段,很明显这需要额外的空间来存储这个版本字段,浪费了空间,但是乐观锁会让系统具有更好的并发性,这是对时间的节省。因此乐观锁也是典型的空间换时间的策略。

来自:操作系统-锁-悲观锁、乐观锁
课程
专栏
专业课-操作系统-锁-悲观锁、乐观锁
3专栏
1课程
4 试题
热门专题