乐观锁(CAS)和悲观锁(synchronized)的详细介绍
1. 锁的定义
在代码中多个线程需要同时操作共享变量,这时需要给变量上把锁,保证变量值是线程安全的。
锁的种类非常多,比如:互斥锁、自旋锁、重入锁、读写锁、行锁、表锁等这些概念,总结下来就两种类型,乐观锁和悲观锁。
2.乐观锁
乐观锁就是持比较乐观态度的锁。在操作数据时非常乐观,认为别的线程不会同时修改数据,只有到数据提交的时候才通过一种机制来验证数据是否存在冲突。一般使用CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
3.悲观锁
比较悲观的锁,总是想着最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。在Java中,synchronized从偏向锁、轻量级锁到重量级锁,全是悲观锁。JDK提供的Lock实现类全是悲观锁。一般用于多写的场景。
4.CAS的介绍
CAS 即 Compare and Swap,它体现的一种乐观锁的思想,比如:多个线程要对一个共享的整型变量执行 +1 操作:
// 需要不断尝试 while (true) {int 旧值 = 共享变量; // 比如拿到了当前值 0int 结果 = 旧值 + 1; // 在旧值 0 的基础上增加 1 ,正确结果是 1/*这时候如果别的线程把共享变量改成了 5,本线程的正确结果 1 就作废了,这时候compareAndSwap 返回 false,重新尝试,直到:compareAndSwap 返回 true,表示本线程做修改的同时,别的线程没有干扰*/if( compareAndSwap ( 旧值, 结果 )) {// 成功,退出循环} }
获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰。结合 CAS 和 volatile 可以实现无锁并发,适用于竞争不激烈、多核 CPU 的场景下。CAS 底层依赖于一个 Unsafe 类来直接调用操作系统底层的 CAS 指令。
java.util.concurrent中提供了原子操作类,可以提供线程安全的操作,例如:AtomicInteger、 AtomicBoolean等,它们底层就是采用 CAS 技术 + volatile 来实现的。
java的内存模型(可见性,原子性,有序性)详细介绍_傻鱼爱编程的博客-CSDN博客这个里面原子性问题,用CAS技术解决方案如下:
// 创建原子整数对象private static AtomicInteger i = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int j = 0; j < 1000; j++) {i.getAndIncrement(); // 获取并且自增 i++}});Thread t2 = new Thread(() -> {for (int j = 0; j < 1000; j++) {i.getAndDecrement(); // 获取并且自减 i--}});t1.start();t2.start();t1.join();t2.join();System.out.println(i);}
5. synchronized的介绍
Java HotSpot 虚拟机中,每个对象都有对象头(包括 class 指针和 Mark Word)。Mark Word 平时存储这个对象的哈希码 、分代年龄,当加锁时,这些信息就根据情况被替换为 标记位 、 线程锁记录指针 、重量级锁指针 、线程ID 等内容。
JDK5引入了CAS原子操作,从JDK6开始对synchronized的实现机制进行了各种优化,包括使用JDK5引进的CAS自旋之外,还增加了自适应的CAS自旋、锁消除、锁粗化、偏向锁、轻量级锁(默认开启偏向锁)这些优化策略。由于此关键字的优化使得性能极大提高,同时语义清晰、操作简单、无需手动关闭,推荐在允许的情况下尽量使用此关键字。
JDK6以后锁主要存在四种状态,依次是:无锁状态(对象头中存储01)、偏向锁状态(对象头中存储线程id)、轻量级锁状态(对象头中存储00)、重量级锁状态(对象头中存储10),锁的升级是单向的。
5.1 偏向锁
Java6中引入了偏向锁来做优化:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。
偏向锁的缺点:
1. 撤销偏向需要将持锁线程升级为轻量级锁,这个过程中所有线程需要暂停(STW)
2. 访问对象的 hashCode 也会撤销偏向锁
3.撤销偏向和重偏向都是批量进行的,以类为单位,如果撤销偏向到达某个阈值,整个类的所有对象都会变为不可偏向的
开启偏向锁(默认开启):-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
关闭偏向锁:-XX:-UseBiasedLocking
举个例子分析一下:
假设有两个方法同步块,利用同一个对象加锁
Object ob = new Object();
public void method1() {synchronized(ob) {// 同步块 Amethod2();}
}
public void method2() {synchronized(ob) {// 同步块 B}
}
解释过程如下:
5.2 轻量级锁
若偏向锁失败,它会尝试使用轻量级锁的优化手段,此时Mark Word 的结构也变为轻量级锁的结构。如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。
轻量级锁竞争的时候,用自旋进行了优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。在Java 6之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。Java 7 之后不能控制是否开启自旋功能。
举个例子分析一下:
假设有两个方法同步块,利用同一个对象加锁
Object ob = new Object();
public void method1() {synchronized(ob) {// 同步块 Amethod2();}
}
public void method2() {synchronized(ob) {// 同步块 B}
}
解释过程如下: (每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word)
5.3 重量锁
如果在尝试加轻量级锁的过程中,CAS 操作无法成功(经过自旋),这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。
乐观锁(CAS)和悲观锁(synchronized)的详细介绍相关推荐
- laravel mysql 悲观锁_[笔记] 悲观锁与乐观锁
在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性. 乐观并发控制(乐观锁)和悲观并发控制(悲 ...
- mysql悲观锁乐观锁定义_悲观锁乐观锁的定义
悲观锁,正如其名,具有强烈的独占和排他特性,它指的是对数据被外界修改持保守态度.乐观锁机制采取了更加宽松的加锁机制,乐观锁是相对悲观锁而言,也是为了避免数据库幻读.业务处理时间过长等原因引起数据处理错 ...
- mysql默认乐观锁悲观锁_MySQL中悲观锁和乐观锁到底是什么?-阿里云开发者社区...
索引和锁是数据库中的两个核心知识点,隔离级别的实现都是通过锁来完成的 按照锁颗粒对锁进行划分 ? 锁用来对数据进行锁定,我们可以从锁定对象的粒度大小来对锁进行划分,分别为行锁.页锁和表锁. 行锁就是按 ...
- mysql中的乐观锁_MySQL中悲观锁和乐观锁到底是什么?
索引和锁是数据库中的两个核心知识点,隔离级别的实现都是通过锁来完成的 按照锁颗粒对锁进行划分 ? 锁用来对数据进行锁定,我们可以从锁定对象的粒度大小来对锁进行划分,分别为行锁.页锁和表锁.行锁就是按照 ...
- 悲观锁代码java_悲观锁的代码实现如何编写?
悲观锁相信大家都不陌生了,之前说过了悲观锁的各种原理,这次我们就来聊聊悲观锁是如何实现的吧. 悲观锁,就跟它的名字意思一样,它非常悲观,它的机制就是对数据被外界(包括本系统当前的其他事务,以及来自外部 ...
- mysql悲观锁关键字_MySQL悲观锁 select for update实现秒杀案例(jfinal框架)
MySQL悲观锁 select for update实现秒杀案例(jfinal框架) 发布时间:2018-08-17作者:laosun阅读(4287) 为了方便测试,博主使用最新的jfinal框架,里 ...
- mysql实现悲观锁语句_mysql悲观锁总结和实践
使用场景举例:以MySQL InnoDB为例 商品t_goods表中有一个字段status,status为1代表商品未被下单,status为2代表商品已经被下单,那么我们对某个商品下单时必须确保该商品 ...
- 无锁CAS及无锁队列实现
CAS ⽐较并交换(compare and swap, CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据 交换操作,从⽽避免多线程同时改写某⼀数据时由于执⾏顺序不确定性以及中断的不可 ...
- thinkphp5 mysql锁机制_thinkphp悲观锁机制处理高并发
问题分析 突然间被运营滴滴说某个活动的报名人数超过了限制人数,问怎么回事,我一下子还挺蒙的,我明明有在报名的操作之前设置了检查如果超过报名人数代码逻辑会抛错继续报名的呀. 然后我又打开数据库看了一下, ...
最新文章
- 京东Flink优化与技术实践
- C++编程语言之Lambda 函数与表达式
- 异或交换值(有趣点)
- 寻找GridView中模板列中的控件
- nginx优化-nginx事件处理模型优化use epoll;
- compare to造句及翻译_compare to造句
- Web Service学习总结
- 服务器常见故障及其解决方法
- 【持续更新】一些常用的网站分享(智能教育装备、智能机器人行业)
- html——陆海网站练习
- 如何安装服务器操作系统 HP服务器系统安装
- 阿里云服务器怎么开发票?
- 解读:【阿里热线小蜜】实时语音对话场景下的算法实践
- ES2015部分新特性示例
- 使用商业智能BI工具有哪些好处?
- 【转】开发者需要了解的领域特定语言(DSL)
- Qt常用轮子合集(带使用Demo)
- 一个完整的LED灯具散热优化分析计算方案
- 【three.js:语法】光源使用详解2-3(聚光灯 SpotLight、平行光 DirectionLight 、环境光 HemisphereLight、镜头光晕 LensFlare)
- 软件架构设计师:用户界面设计的原则