悲观锁和乐观锁的比较和使用
悲观锁(Pessimistic Lock) 顾名思义,就是 很悲观 ,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,
悲观锁(Pessimistic Lock)
顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
我们认为系统中的并发更新会非常频繁,并且事务失败了以后重来的开销很大,这样以来,我们就需要采用真正意义上的锁来进行实现。悲观锁的基本思想就是每次一个事务读取某一条记录后,就会把这条记录锁住,这样其它的事务要想更新,必须等以前的事务提交或者回滚解除锁。
实现方式:
大多在数据库层面实现加锁操作,JDBC方式:在JDBC中使用悲观锁,需要使用select for update
语句,e.g.
<code class="sql hljs "><span class="hljs-operator"><span class="hljs-keyword">Select</span> * <span class="hljs-keyword">from</span> Account <span class="hljs-keyword">where</span> ...(<span class="hljs-keyword">where</span> condition).. <span class="hljs-keyword">for</span> <span class="hljs-keyword">update</span></span></code>
乐观锁(Optimistic Lock)
顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
我们认为系统中的事务并发更新不会很频繁,即使冲突了也没事,大不了重新再来一次。它的基本思想就是每次提交一个事务更新时,我们想看看要修改的东西从上次读取以后有没有被其它事务修改过,如果修改过,那么更新就会失败。
实现方式:
大多是基于数据版本(Version)记录机制实现,何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。
读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提 交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据 版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
假如系统中有一个Account的实体类,我们在Account中多加一个version字段,那么我们JDBC Sql语句将如下写:
e.g.
<code class="sql hljs "><span class="hljs-operator"><span class="hljs-keyword">Select</span> a.version....<span class="hljs-keyword">from</span> Account <span class="hljs-keyword">as</span> a <span class="hljs-keyword">where</span> (<span class="hljs-keyword">where</span> condition..) <span class="hljs-keyword">Update</span> Account <span class="hljs-keyword">set</span> version = version+<span class="hljs-number">1.</span>....(another field) <span class="hljs-keyword">where</span> version =?...(another contidition)</span></code>
这样以来我们就可以通过更新结果的行数来进行判断,如果更新结果的行数为0,那么说明实体从加载以来已经被其它事务更改了,所以就抛出自定义的乐观锁定异常。具体实例如下:
<code class="java hljs "><span class="hljs-keyword">int</span> rowsUpdated = statement.executeUpdate(sql); <span class="hljs-keyword">if</span> (rowsUpdated ==<span class="hljs-number">0</span> ) { <span class="hljs-keyword">throws</span> <span class="hljs-keyword">new</span> OptimisticLockingFailureException(); }</code>
悲观锁的实现
Synchronized互斥锁属于悲观锁,它有一个明显的缺点,它不管数据存不存在竞争都加锁,随着并发量增加,且如果锁的时间比较长,其性能开销将会变得很大。有没有办法解决这个问题?答案就是基于冲突检测的乐观锁。这种模式下,已经没有所谓的锁概念了,每条线程都直接先去执行操作,计算完成后检测是否与其他线程存在共享数据竞争,如果没有则让此操作成功,如果存在共享数据竞争则可能不断地重新执行操作和检测,直到成功为止,这种叫做CAS自旋。
Java里的CompareAndSet(CAS)
以AtomicInteger的incrementAndGet的实现为例:
incrementAndGet的实现
<code class="java hljs "> <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> <span class="hljs-title">incrementAndGet</span>() { <span class="hljs-keyword">for</span> (;;) { <span class="hljs-keyword">int</span> current = get(); <span class="hljs-keyword">int</span> next = current + <span class="hljs-number">1</span>; <span class="hljs-keyword">if</span> (compareAndSet(current, next)) <span class="hljs-keyword">return</span> next; } }</code>
首先可以看到他是通过一个无限循环(spin)直到increment成功为止。
循环的内容是:
- 取得当前值
- 计算+1后的值
- 如果当前值还有效(没有被)的话设置那个+1后的值
- 如果设置没成功(当前值已经无效了即被别的线程改过了), 再从1开始。
compareAndSet的实现
<code class="java hljs "><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">compareAndSet</span>(<span class="hljs-keyword">int</span> expect, <span class="hljs-keyword">int</span> update) { <span class="hljs-keyword">return</span> unsafe.compareAndSwapInt(<span class="hljs-keyword">this</span>, valueOffset, expect, update); }</code>
直接调用的是UnSafe这个类的compareAndSwapInt方法,全称是sun.misc.Unsafe。这个类是Oracle(Sun)提供的实现,可能在别的公司的JDK里就不是这个类了。
compareAndSwapInt的实现
<code class="c hljs "> <span class="hljs-comment">/** * Atomically update Java variable to <tt>x</tt> if it is currently * holding <tt>expected</tt>. * @return <tt>true</tt> if successful */</span> <span class="hljs-keyword">public</span> final native boolean compareAndSwapInt(Object o, <span class="hljs-keyword">long</span> offset, <span class="hljs-keyword">int</span> expected, <span class="hljs-keyword">int</span> x); </code>
此方法不是Java实现的,而是通过JNI调用操作系统的原生程序,涉及到CPU原子操作,现在几乎所有的CPU指令都支持CAS的原子操作,X86下对应的是CMPXCHG汇编指令。
出于好奇,查看了下CAS原子操作的代码描述:
<code class="c hljs "><span class="hljs-keyword">int</span> compare_and_swap(<span class="hljs-keyword">int</span>* reg, <span class="hljs-keyword">int</span> oldval, <span class="hljs-keyword">int</span> newval) { ATOMIC(); <span class="hljs-keyword">int</span> old_reg_val = *reg; <span class="hljs-keyword">if</span> (old_reg_val == oldval) *reg = newval; END_ATOMIC(); <span class="hljs-keyword">return</span> old_reg_val; }</code>
也就是检查内存*reg里的值是不是oldval,如果是的话,则对其赋值newval。上面的代码总是返回old_reg_value,调用者如果需要知道是否更新成功还需要做进一步判断,为了方便,它可以变种为直接返回是否更新成功,如下:
<code class="c hljs "><span class="hljs-keyword">bool</span> compare_and_swap (<span class="hljs-keyword">int</span> *accum, <span class="hljs-keyword">int</span> *dest, <span class="hljs-keyword">int</span> newval) { <span class="hljs-keyword">if</span> ( *accum == *dest ) { *dest = newval; <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; } <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>; }</code>
两种锁的比较
两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

如今手机的性能和功能越来越强大,几乎所有手机都配备了便捷的NFC功能,方便用户进行移动支付和身份认证。然而,有些小米14Pro的用户可能不清楚如何启用NFC功能。接下来,让我详细向大家介绍一下。小米14Pro怎么开启nfc功能?步骤一:打开手机的设置菜单。步骤二:找到并点击“连接和共享”或“无线和网络”选项。步骤三:在连接和共享或无线和网络菜单中,找到并点击“NFC和支付”。步骤四:找到并点击“NFC开关”。一般情况下,默认是关闭的状态。步骤五:在NFC开关页面上,点击开关按钮,将其切换为开启状

CrystalDiskMark是一款适用于硬盘的小型HDD基准测试工具,可以快速测量顺序和随机读/写速度。接下来就让小编为大家介绍一下CrystalDiskMark,以及crystaldiskmark如何使用吧~一、CrystalDiskMark介绍CrystalDiskMark是一款广泛使用的磁盘性能测试工具,用于评估机械硬盘和固态硬盘(SSD)的读写速度和随机I/O性能。它是一款免费的Windows应用程序,并提供用户友好的界面和各种测试模式来评估硬盘驱动器性能的不同方面,并被广泛用于硬件评

foobar2000是一款能随时收听音乐资源的软件,各种音乐无损音质带给你,增强版本的音乐播放器,让你得到更全更舒适的音乐体验,它的设计理念是将电脑端的高级音频播放器移植到手机上,提供更加便捷高效的音乐播放体验,界面设计简洁明了易于使用它采用了极简的设计风格,没有过多的装饰和繁琐的操作能够快速上手,同时还支持多种皮肤和主题,根据自己的喜好进行个性化设置,打造专属的音乐播放器支持多种音频格式的播放,它还支持音频增益功能根据自己的听力情况调整音量大小,避免过大的音量对听力造成损害。接下来就让小编为大

网易邮箱,作为中国网民广泛使用的一种电子邮箱,一直以来以其稳定、高效的服务赢得了用户的信赖。而网易邮箱大师,则是专为手机用户打造的邮箱软件,它极大地简化了邮件的收发流程,让我们的邮件处理变得更加便捷。那么网易邮箱大师该如何使用,具体又有哪些功能呢,下文中本站小编将为大家带来详细的内容介绍,希望能帮助到大家!首先,您可以在手机应用商店搜索并下载网易邮箱大师应用。在应用宝或百度手机助手中搜索“网易邮箱大师”,然后按照提示进行安装即可。下载安装完成后,我们打开网易邮箱账号并进行登录,登录界面如下图所示

在如今云存储已经成为我们日常生活和工作中不可或缺的一部分。百度网盘作为国内领先的云存储服务之一,凭借其强大的存储功能、高效的传输速度以及便捷的操作体验,赢得了广大用户的青睐。而且无论你是想要备份重要文件、分享资料,还是在线观看视频、听取音乐,百度网盘都能满足你的需求。但是很多用户们可能对百度网盘app的具体使用方法还不了解,那么这篇教程就将为大家详细介绍百度网盘app如何使用,还有疑惑的用户们就快来跟着本文详细了解一下吧!百度云网盘怎么用:一、安装首先,下载并安装百度云软件时,请选择自定义安装选

MetaMask(中文也叫小狐狸钱包)是一款免费的、广受好评的加密钱包软件。目前,BTCC已支持绑定MetaMask钱包,绑定后可使用MetaMask钱包进行快速登入,储值、买币等,且首次绑定还可获得20USDT体验金。在BTCCMetaMask钱包教学中,我们将详细介绍如何注册和使用MetaMask,以及如何在BTCC绑定并使用小狐狸钱包。MetaMask钱包是什么?MetaMask小狐狸钱包拥有超过3,000万用户,是当今最受欢迎的加密货币钱包之一。它可免费使用,可作为扩充功能安装在网络

iPhone16Pro的CAD文件已经曝光,设计与先前的传闻一致。去年秋天,iPhone15Pro新增了Action按钮,而今年秋天,Apple似乎计划对这款硬件的尺寸进行微小的调整。加入Capture按钮据传言,iPhone16Pro可能会新增第二个新按钮,这将是继去年之后连续第二年增加新按钮。传闻称新的Capture按钮将被设置在iPhone16Pro的右下侧,这一设计有望让相机控制更加便捷,同时还能让Action按钮用于其他功能。这个按钮将不再仅仅是一个普通的快门按钮。关于相机,从目前iP

隔空滑动屏幕是华为的一项功能,在华为mate60系列中可以说是备受好评,这个功能是通过利用手机上的激光感应器和前置摄像头的3D深感摄像头,来完成一系列不需要触碰屏幕的功能,比如说隔空刷抖音,但是华为Pocket2应该要怎么隔空刷抖音呢?华为Pocket2怎么隔空截图?1、打开华为Pocket2的设置2、然后选择【辅助功能】。3、点击打开【智慧感知】。4、打开【隔空滑动屏幕】、【隔空截屏】、【隔空按压】开关就可以了。5、在使用的时候,需要再距离屏幕20~40CM处,张开手掌,待屏幕上出现手掌图标,
