java多线程系列3:悲观锁和乐观锁
1.悲观锁和乐观锁的基本概念
悲观锁:
- 总是认为当前想要获取的资源存在竞争(很悲观的想法),因此获取资源后会立刻加锁,于是其他线程想要获取该资源的时候就会一直阻塞直到能够获取到锁;
- 在传统的关系型数据库中,例如行锁、表锁、读锁、写锁等,都用到了悲观锁。还有java中的同步关键字Synchronized也是一种悲观锁;
乐观锁:
- 总是认为当前想要获取的资源不存在竞争(很乐观的想法),因此在获取资源后,并不会加锁;
- 但是在执行更新操作时,会判断在这期间是否有其他人更新过这个数据,可使用版本号等机制实现;
- 适用于多读的应用程序,可提高吞吐量;
- 像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
2.乐观锁的一种实现方式:CAS
因为乐观锁的思想是:在通常情况下都认为不会产生并发冲突,因此在对数据进行提交更新的时候,会对将要提交更新的数据进行并发冲突检测、如果冲突存在,则会返回错误信息给用户,让用户决定处理方式。
基于乐观锁的思想,我们可以知道乐观锁实现的步骤包含两个部分:冲突检测和数据更新,而CAS就是其中一个典型的实现方式.
CAS:Compare And Swap(比较并交换)
CAS是一种乐观锁技术。当多个线程使用CAS尝试更新同一个变量时,只有一个线程能够成功更新,其他线程都会失败,但是失败的线程并不会挂起,而是被告知在此次竞争中失败并可再次尝试。
CAS包含三个操作数:

在JDK1.5中新增的java.util.concurrent包中的内容就是建立早CAS基础之上的,相对于Synchronized的阻塞式算法,CAS其实是一种非阻塞算法的实现,因此java.util.concurrent包中组件的性能大大提升。
下面以java.util.concurrent中的AtomicInteger的getAndIncrement(该操作相当于变量自加) 为例,看一下在不加锁的情况下,如何保证线程安全:
public class AtomicInteger extends Number implements java.io.Serializable {
private volatile int value;
public final int get() {
return value;
}
public final int getAndIncrement() {
//自旋方式采用CAS来修改当前值,直到成功为止
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);
}
}
如果compareAndSet(current, next)方法成功执行,则直接返回;如果线程竞争激烈,导致compareAndSet(current, next)方法一直不能成功执行,则会一直循环等待。
3.CAS存在的问题
①. ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

②.循环时间开销大:因为CAS中存在自旋,当自旋长时间不成功时,会给CPU带来极大开销,如果CPU执行支持pause指令,效率能够得到提升。
pause指令作用1:延迟流水线执行指令,(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。
pause指令作用2:可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
③.只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
4.CAS与Synchronized的选择
1、线程冲突严重时,使用CAS等乐观锁,自旋几率较大,会因为自旋浪费更多的CPU资源;此时使用Synchronized等悲观锁性能较好。
2、线程冲突较轻时,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源,而自旋概率较小,使用CAS性能高于同步锁。
java多线程系列3:悲观锁和乐观锁的更多相关文章
- java多线程----悲观锁与乐观锁
java多线程中悲观锁与乐观锁思想 一.悲观锁 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线 ...
- 多线程深入:乐观锁与悲观锁以及乐观锁的一种实现方式-CAS(转)
原文:https://www.cnblogs.com/qjjazry/p/6581568.html 首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每 ...
- Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很 ...
- Java中的锁-悲观锁、乐观锁,公平锁、非公平锁,互斥锁、读写锁
总览图 如果文中内容有错误,欢迎指出,谢谢. 悲观锁.乐观锁 悲观锁.乐观锁使用场景是针对数据库操作来说的,是一种锁机制. 悲观锁(Pessimistic Lock):顾名思义,就是很悲观,每次去拿数 ...
- Java锁之乐观锁、悲观锁、自旋锁
java锁分为三大类乐观锁.悲观锁.自旋锁 乐观锁:乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别 ...
- Java多线程系列--“JUC锁”03之 公平锁(一)
概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...
- Java多线程系列--“JUC锁”04之 公平锁(二)
概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...
- Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例
概要 本章介绍JUC包中的CyclicBarrier锁.内容包括:CyclicBarrier简介CyclicBarrier数据结构CyclicBarrier源码分析(基于JDK1.7.0_40)Cyc ...
- Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock
本章对ReentrantLock包进行基本介绍,这一章主要对ReentrantLock进行概括性的介绍,内容包括:ReentrantLock介绍ReentrantLock函数列表ReentrantLo ...
随机推荐
- cube-ui indexList的正确使用
demo地址:https://github.com/zphtown/cube-ui-bug 上拉和下拉核心代码: onPullingDown () { this.isNoMore = false th ...
- Centos7下安装ZooKeeper
1.下载源码 zookeeper 需要jdk的支持,需要先安装jdk 官网下载地址: http://mirrors.hust.edu.cn/apache/zookeeper/ 选择最新的版本进行下载 ...
- linux下ifconfig只剩下lo的解决方法
运行以下命令,删除旧的网络配置,重新让networkmanager自动配置即可 sudo service network-manager stop sudo rm /var/lib/NetworkMa ...
- 无Xwindow的linux系统安装VMware Tools
首先登陆linux 系统,最好是root用户: 然后挂载cdrom: mkdir -p /mnt/cdrom mount -t iso9660 /dev/cdrom /mnt/cdrom 进入cdro ...
- 7、Linux权限管理-基本权限
1.权限概述 1.1.什么是权限? 我们可以把它理解为操作系统对用户能够执行的功能所设立的限制,主要用于约束用户能对系统所做的操作,以及内容访问的范围,或者说,权限是指某个特定的用户具有特定的系统资源 ...
- PyCharm上unittest 测试用例两种执行方式
每次讲到unittest测试框架的时候,经常有同学在群里反映,为啥我的运行不是按照我往suite里添加的用例顺序执行的,为什么别人的能跑出来报告,我的就不行... 当然,首先确认代码是OK的,如果代码 ...
- log4net 报错
之前在网上学习了一种log4net的日志监控,这种方式我觉得很不错,至少我个人认为很好,但是最紧缺发现一个为题,就是再session过期的时候页面跳转时候 会报错,这个搞了很久没搞明白,我直接用例子讲 ...
- ecshop新版不能在模板文件.dwt和.lbi中直接添加php代码的解决方法
ecshop新版不能在模板文件.dwt和.lbi中直接添加php代码了,为什么呢? 因为直接在模板中加入php函数和代码,没有经过过滤,容易造成安全隐患.程序源码安全是非常重要的. 不过如果有朋友希望 ...
- 本地安装sass出错问题解析
2016年3月23日个人博客文章--迁移到segmentfault 安装sass ruby安装因为sass依赖于ruby环境,所以装sass之前先确认装了ruby.先导官网下载个ruby 在安装的时候 ...
- Bash快捷操作
编辑命令 Ctrl + a :移到命令行首 Ctrl + e :移到命令行尾 Ctrl + f :按字符前移(右向) Ctrl + b :按字符后移(左向) Alt + f :按单词前移(右向) Al ...