概述

  乐观锁采用的是一种无锁的思想,总是假设最好的情况,认为一个事务在读取数据的时候,不会有别的事务对数据进行修改,只需要在修改数据的时候判断原数据数据是否已经被修改了。JDK 中 java.util.concurrent.atomic 包下提供的原子类就是基于乐观锁的思想实现的,具体的实现主要依赖于 sun.misc.Unsafe 类提供的 CAS(Compare And Swap) 算法实现。

CAS 算法

  Compare And Swap,顾名思义,先比较然后交换。CAS 算法主要的参数有三个:old(原值)、expect(期望值)、update(更新值)。首先比较 expect(期望值)与  old(原值)是否相等,若相等,则将 old(原值)更新为 update(更新值)。

  JDK 1.8 中 sun.misc.Unsafe 类提供了以下几种 CAS 算法相关的方法

  1) 最底层的方法(native method):

  public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);

  public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);

  public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);

  首先介绍一下这四个参数:

  • 参数 1: 表示指向共享变量的引用
  • 参数 2: 表示距离共享变量内存储存开始位置的偏移量,指向该变量对象的某一具体属性。obj + offset 可以确定 old(原值)
  • 参数 3:表示 expect(期望值),用于与 old(原值)判断是否相等
  • 参数 4:表示 update(更新值),若 expect 和 old 相等,则将 old 更新为 update

  返回值:若 expect 与 old 判断相等,且将 old 更新为 update,则返回 true;否则返回 false。

  2) JDK 1.8 新增方法:

  public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
{
int i;
do
i = getIntVolatile(paramObject, paramLong);
while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
return i;
} public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2)
{
long l;
do
l = getLongVolatile(paramObject, paramLong1);
while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2));
return l;
} public final int getAndSetInt(Object paramObject, long paramLong, int paramInt)
{
int i;
do
i = getIntVolatile(paramObject, paramLong);
while (!compareAndSwapInt(paramObject, paramLong, i, paramInt));
return i;
} public final long getAndSetLong(Object paramObject, long paramLong1, long paramLong2)
{
long l;
do
l = getLongVolatile(paramObject, paramLong1);
while (!compareAndSwapLong(paramObject, paramLong1, l, paramLong2));
return l;
} public final Object getAndSetObject(Object paramObject1, long paramLong, Object paramObject2)
{
Object localObject;
do
localObject = getObjectVolatile(paramObject1, paramLong);
while (!compareAndSwapObject(paramObject1, paramLong, localObject, paramObject2));
return localObject;
}

  对比 JDK 1.7 和 1.8 中原子类 AtomicInteger 的代码实现: JDK 1.8 将自旋操作封装到 sun.misc.Unsafe 类中

  JDK1.7:

  public final int getAndIncrement()
{
while (true)
{
int i = get();
int j = i + 1;
if (compareAndSet(i, j))
return i;
}
}

  JDK 1.8:

    public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}

注意

  JDK 中乐观锁的实现思路:使用 volatile 关键字修饰共享变量 + sun.misc.Unsafe 类提供的 CAS 算法实现。volatile 关键字保证了线程的可见性,在进行 CAS 算法 “比较” 时,保证当前线程获取到的是最新的值;CAS 算法中 Compare (比较) 和 Swap(交换)是一组原子操作,保证了线程的原子性。

CAS 与 ABA 问题

  ABA 问题指的是:一个线程将变量 A 修改为变量 B,然后又修改为变量 A,前后两个 A 可能修改了某些属性,已经发生了变更;另一个线程拿到仍然是变量 A,CAS 成功,但实际上另一线程拿到的变量 A可能已经被修改过的。

  JDK 中提供了解决 ABA 问题的具体实现:java.util.concurrent.atomic.AtomicMarkableReference<V> 和 java.util.concurrent.atomic.AtomicStampedReference<V>。

原子类 AtomicInteger 源码分析

 1). 初始化:定义了一个 volatile 关键字修饰的 int 变量 value,获取 Unsafe 对象和变量 value 的内存地址偏移量 valueOffset

    // setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
} private volatile int value; /**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
} /**
* Creates a new AtomicInteger with initial value {@code 0}.
*/
public AtomicInteger() {
}

  2). 常用方法:使用 Unsafe 类的 CAS 算法实现

    public final int get() {
return value;
} public final void set(int newValue) {
value = newValue;
} public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
} public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
} public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
} public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
} public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
} public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
} public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
} public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}

  

B8 Concurrent JDK中的乐观锁与原子类的更多相关文章

  1. mysql中的乐观锁和悲观锁

    mysql中的乐观锁和悲观锁的简介以及如何简单运用. 关于mysql中的乐观锁和悲观锁面试的时候被问到的概率还是比较大的. mysql的悲观锁: 其实理解起来非常简单,当数据被外界修改持保守态度,包括 ...

  2. Java中的乐观锁

    1.前言 之前好几次看到有人在面经中提到了乐观锁与悲观锁,但是一本<Java Concurrency In Practice>快看完了都没有见到过这两种锁,今天终于在第15章发现了它们的踪 ...

  3. MYSQL中的乐观锁实现(MVCC)简析

    https://segmentfault.com/a/1190000009374567#articleHeader2 什么是MVCC MVCC即Multi-Version Concurrency Co ...

  4. 老司机带大家领略MySQL中的乐观锁和悲观锁

    原文地址:https://cloud.tencent.com/developer/news/227982 为什么需要锁 在并发环境下,如果多个客户端访问同一条数据,此时就会产生数据不一致的问题,如何解 ...

  5. MySQL/InnoDB中,乐观锁、悲观锁、共享锁、排它锁、行锁、表锁、死锁概念的理解

    文章出处:https://www.souyunku.com/2018/07/30/mysql/?utm_source=tuicool&utm_medium=referral MySQL/Inn ...

  6. 利用MySQL中的乐观锁和悲观锁实现分布式锁

    背景 对于一些并发量不是很高的场景,使用MySQL的乐观锁实现会比较精简且巧妙. 下面就一个小例子,针对不加锁.乐观锁以及悲观锁这三种方式来实现. 主要是一个用户表,它有一个年龄的字段,然后并发地对其 ...

  7. Conccrent中 Unsafe类原理 以及 原子类AutomicXX的原理以及对Unsafe类的使用

    Unsafe类的介绍 Java中基于操作系统级别的原子操作类sun.misc.Unsafe,它是Java中对大多数锁机制实现的最基础类.请注意,JDK 1.8和之前JDK版本的中sun.misc.Un ...

  8. 乐观锁悲观锁对应的JAVA代码和数据库

    乐观锁悲观锁是一种思想.可以用在很多方面. 比如数据库方面.悲观锁就是for update乐观锁就是 version字段 JDK方面:悲观锁就是sync乐观锁就是原子类(内部使用CAS实现) 本质来说 ...

  9. 乐观锁和 MVCC 的区别?

    二者不是一个层面的东西. MVCC(Multi-Version Concurrent Control),基于快照隔离机制(Snapshot Isolations)进行多版本并发控制,是一种以乐观锁为理 ...

随机推荐

  1. PHP mysqli_commit() 函数

    关闭自动提交,做一些查询,然后提交查询: <?php // 假定数据库用户名:root,密码:123456,数据库:RUNOOB $con=mysqli_connect("localh ...

  2. 002_linux之点灯(汇编深度解析)

    1.      开发板采用韦山东的开发板 2.      芯片CPU三星S3C2440A 3.  控制引脚:GPF4 4.  linux操作系统 5. 芯片手册下载地址:https://eyun.ba ...

  3. java文件实现文件的上传和下载

    文件上传是最古老的互联网操作之一,20多年来几乎没有怎么变化,还是操作麻烦.缺乏交互.用户体验差. 一.前端代码 英国程序员Remy Sharp总结了这些新的接口 ,本文在他的基础之上,讨论在前端采用 ...

  4. 7月清北学堂培训 Day 3

    今天是丁明朔老师的讲授~ 数据结构 绪论 下面是天天见的: 栈,队列: 堆: 并查集: 树状数组: 线段树: 平衡树: 下面是不常见的: 主席树: 树链剖分: 树套树: 下面是清北学堂课程表里的: S ...

  5. Devexpress GridControl中 repositoryItemCheckEdit作为选择列以及作为显示列的使用方法

    一.在gridcontrol列表控件中使用单选框作为选择列,这里有两种方式. 方式一:选择gridcontrol控件的Run Designer按钮,添加一列,设置该列的ColumnEdit为check ...

  6. 微信小程序之简单记账本开发记录(二)

    1.打开开发者工具 2.从微信公众平台上获取到appid,或者使用测试号. 项目的大致目录如下: 一个小程序主体部分由三个文件组成,必须放在项目的根目录中 以app为开头的文件名用来布置作用于整个项目 ...

  7. 手把手教你实现RecyclerView的下拉刷新和上拉加载更多

    手把手教你实现RecyclerView的下拉刷新和上拉加载更多     版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明. 本文链接:https:// ...

  8. IO注意事项

    read()方法返回值为什么是int? 因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式的存储的,如果每次读取都返回byte,有可能在读到中间的时候遇到1111111 ...

  9. 连接池中的maxIdle,MaxActive,maxWait参数

    连接池中的maxIdle,MaxActive,maxWait参数 线程池 name:表示你的连接池的名称也就是你要访问连接池的地址 auth:是连接池管理权属性,Container表示容器管理 typ ...

  10. oracle数据库使用PL/sql导入excel数据

    1.打开PL/SQL工具,菜单进入   工具>ODBC导入器(菜单列表倒数第二): 2.连接数据库与选择导入的excel文件表: 3.选择导入的表以及excel与数据库表字段之间的对应关系: