热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何实现乐观锁,cas和锁的区别

文中加入了个人理解,如有不准确的地方欢迎提出,笔者会及时的进行改正。乐观锁与悲观锁乐观锁:假设数据不会发生冲突,只有在进行数据更新的才会对数据进行检查,如果冲突则更新失败并返回错


正文中加入个人理解,如有不准确之处欢迎提出,笔者及时修改。


乐观锁定和悲观锁定乐观锁定:仅在数据更新发生时检查数据,如果发生冲突,更新失败并返回错误消息


悲观锁定:悲观锁定与乐观锁定相反,因为它假设每次都修改资源,所以在访问资源之前先对其进行锁定,以便在其他人尝试访问资源时阻止其直到解除锁定。


比较锁定(cascas )是乐观锁定的实现方法之一。


CAS轻量级锁定在想要更新变量时判断线程内存中的变量与公用内存中的变量的值是否相等,如果相同则进行修正,如果不同,则重新读取变量的值并重复上述操作。


CAS的优缺点CAS的优点很明显,CAS减少了线程之间的上下文切换消耗,避免了线程在一个线程上占用公共资源,并阻止了所有其他线程


从CAS的缺点上图中可以看到,如果其他线程继续修改公共变量,本线程将不断旋转,浪费CPU资源。


说到ABA问题CAS一定要提到ABA问题,首先通过下图来理解什么是ABA问题


线程1获取时变量为a,线程2将变量更新为b,线程3还将变量设置为a。 此时,线程1判断变量是否为原始值并更新为c。


此操作看起来没有问题,即使是变量,对于基本数据类型也几乎没有问题,但如果变量是引用类型,则该字段更改可能会导致严重后果。


那么如何解决ABA问题呢?


通常,添加版本号以确定此公用变量是否已更改。 sql代码示例:


更新表集值=new value,vision=vision1where value=# { old value } and vision=# { vision }在juc中也对应的类3360atomicsttion


publicclassatomictest { publicfinalstaticatomicstampedreferencestringatomic _ reference=newatomicstampedreferencestring (' ) 创建cstaticvoidmain (字符串[ ] args )//线程池threadpoolexecutorthreadpoolexecutor=newthreadpoolexecutor ) 150,150, Executors.defaultThreadFactory ),newthreadpoolexecutor.discard policy ) ); for(intI=0; i 100; I ) { final int num=i; //获取原始versiOnfinalintstamp=atomic _ reference.getstamp (); thread pool executor.execute ((-{ try } thread.sleep ) math.ABS ) int ) ) Math.random ) * 100 ) ); } catch (互联互通) { e.printStackTrace ); 将value与version进行比较,并将其设置为新值if (atomic _ reference.compareandset (ABC )、abc2)、stamp、stamp 1) ) system.out; (); //下一个线程执行后续操作threadPoolExecutor.execute ()-(in tstamp=atomic _ reference.getstamp ),以将数据恢复为原始值; while (! atomic _ reference.compareandset (ABC 2、ABC、stamp、stamp 1); System.out.println ('已恢复为原始值! ' ); ); }} CAS的原子性

p>同时 CAS 的判断与写入操作必须本身保证是原子的,否则在判断和修改变量时其他线程对公共变量进行了修改又会导致数据不安全。

JUC 的 atomic 包下大量使用了 CAS ,我们通过 AtomicInteger 的底层源码来看看 CAS 是如何实现原子性的。

new AtomicInteger().getAndIncrement();

我们点进 getAndIncrement方法

private static final Unsafe U = Unsafe.getUnsafe();public final int getAndIncrement() { return U.getAndAddInt(this, VALUE, 1);}

我们可以看到,这里实际上是调用了 Unsafe 类的方法,Unsafe 类是 Java 的一个后门,由于 java 不能直接操作内存,java 中有许多native 方法用来直接调用 c、c++的方法库从而直接操作内存,Unsafe 中的方法基本都是 native 的。

我们不断再向里点

@HotSpotIntrinsicCandidate public final native boolean compareAndSetInt(Object o, long offset,int expected,int x);

最终发现了一个本地的方法,这个方法调用了调用了 C、C++ 库中的方法。

实际上 C、C++ 库中的方法最终调用的是汇编语言中的 cmpxchg 方法,也就是说 cpu 本身就提供了 CAS 的相关指令。

但是 cmpxchg 指令本身也不能保证原子性,比如两个 cpu 同时进行上述的 cas 操作有可能也会在进行修改的时候被另一个 cpu 打断。但是 C、C++ 库中的方法在 cmpxchg 前加了一个 LOCK_IF_MP 使有多个CPU的时候在加上一把 lock 锁,lock锁会在一条CPU进行CAS 操作的时候锁死总线,这样其他 CPU 就无法操作。

总之 CAS 的原子性底层实现就是通过总线锁实现的。

synchronized

java 中 synchronized 就是悲观锁的一个具体的实现。

我们从 synchronized 锁的对象底层原理锁升级来解释。

锁对象分类 对于普通方法,锁是当前对象对于静态方法,锁是当前对象的类的Class对象对于同步代码块,锁是括号中的对象 重量级锁底层原理

首先我们了解一下一个对象在堆内存中存储的结构:对象头实例数据对齐填充

对象头中存储了:对象Hash值、GC年龄计数器、锁的信息、指向对象类型的索引、(数组还会有数组长度)

任何一个对象都有一个 monitor 对象与之关联,而当一个线程拿到锁时,monitor对象的 _Owner 就会指向该线程,其他线程想要拿到锁就会进入 _EntryList 并被阻塞 ,当前线程释放锁后 _EntryList 中的线程会对锁进行争抢(synchronized是一种非公平锁,也就是说不管这个线程是否是先来的,在争抢锁时机会时相同的,而公平锁则是按照先来后到的方式来获取锁)。

对于同步代码块来说,代码最终会编译成 monitorenter 和 monitorexit 指令,monitorenter 对应的就是线程尝试获取锁的过程,而 monitorexit 则是释放锁的过程。

对于方法而言,它的实现方法在 JVM 中没有详细说明,但是也可用通过 monitorenter 与 monitorexit 来实现。

锁的升级

尽管 synchronized 被我们称为是重量级锁,但是自从 jdk1.6 对 synchronized 进行优化后,synchronized 就变得没有那么“重”了。

我们先得出一个结论:能使用 synchronized 就尽量使用 synchronized

具体的原因是 synchronized 在被优化过后不会一上来就使用重量级锁,而是按照:

偏向锁 -> 轻量级锁 -> 重量级锁

的顺序不断升级。

偏向锁

在有些情况下,锁并不会被不同的线程不断竞争,而是不断被一个线程获取,这样锁的获取和释放就会带来不必要的开销,所以偏向锁就是为了解决这种场景而出现的。

我们在底层原理说到过,对象头中存储了锁的相关信息,其中有 1bit 就是用于存储当前是否是偏向锁(偏向锁表示),2bit用于存储锁标志位(偏向锁对应为 01),在偏向锁的情况下还存储了当前偏向的线程ID,当一个线程想要获取锁首先会判断一下锁对象的偏向线程是否是自己,如果是则代表当前线程已经获取了锁,如果不是则会通过偏向锁标识先判断一下当前是否是偏向锁模式,如果设置了,则尝试使用 CAS 操作将对象头中的线程ID设置为自己的。

偏向锁采用了一种直到产生竞争才会释放锁的机制,也就是说,在尝试使用 CAS 操作将对象头指向自己时其他线程也进行了同样的操作,即产生了冲突,此时偏向锁就会撤销。

撤销的过程:CAS 发生冲突后,拥有偏向锁的线程会在安全点被暂停,此时会检查该线程的状态,如果不在运行则将对象头中的线程置为无锁状态;如果在运行要么对象头重新偏向其他线程,要么恢复到无锁状态,要么标记对象不适合作为偏向锁并升级,最后唤醒暂停的线程。

轻量级锁

轻量级锁的加锁和解锁都运用到了上文提到的 CAS 操作

加锁: 线程在执行同步代码前会先在虚拟机栈的栈帧中创建存储锁记录的空间,并尝试将对象头中的 清脆的翅膀 Work 复制到锁记录中。然后线程会尝试 CAS 操作来将对象头中的指针替换为指向当前锁记录的指针,成功则获取锁,失败则尝试自旋获取锁。

解锁: 尝试将线程中的锁记录替换回对象头,成功则表示没有竞争发生,失败则锁或膨胀为重量级锁(自旋达到一定次数)。

总结

锁优点缺点适用场景偏向锁加锁和解锁不需要额外的消耗,执行速度和非同步代码几乎没有差别如果线程间存在竞争,则带来了撤销锁的消耗只有一个线程访问同步代码轻量级锁竞争的线程不会阻塞,提高了程序的响应速度可能会造成竞争锁带来的自旋,消耗CPU资源追求响应时间,同步代码执行速度快重量级锁线程竞争不会被自旋,不会消耗CPU线程阻塞,相应时间慢追求吞吐量,同步代码执行速度慢可重入锁与不可重入锁 公平锁与非公平锁 自旋锁 互斥锁 读写锁
推荐阅读
  • 处理Android EditText中数字输入与parseInt方法
    本文探讨了如何在Android应用中从EditText组件安全地获取并解析用户输入的数字,特别是用于设置端口号的情况。通过示例代码和异常处理策略,展示了有效的方法来避免因非法输入导致的应用崩溃。 ... [详细]
  • 1、编写一个Java程序在屏幕上输出“你好!”。programmenameHelloworld.javapublicclassHelloworld{publicst ... [详细]
  • 本文基于Java官方文档进行了适当修改,旨在介绍如何实现一个能够同时处理多个客户端请求的服务端程序。在前文中,我们探讨了单客户端访问的服务端实现,而本篇将深入讲解多客户端环境下的服务端设计与实现。 ... [详细]
  • 本文详细介绍了如何使用C#实现不同类型的系统服务账户(如Windows服务、计划任务和IIS应用池)的密码重置方法。 ... [详细]
  • 本文详细探讨了在Java中如何将图像对象转换为文件和字节数组(Byte[])的技术。虽然网络上存在大量相关资料,但实际操作时仍需注意细节。本文通过使用JMSL 4.0库中的图表对象作为示例,提供了一种实用的方法。 ... [详细]
  • 本文详细介绍了 `org.apache.tinkerpop.gremlin.structure.VertexProperty` 类中的 `key()` 方法,并提供了多个实际应用的代码示例。通过这些示例,读者可以更好地理解该方法在图数据库操作中的具体用途。 ... [详细]
  • Beetl是一款先进的Java模板引擎,以其丰富的功能、直观的语法、卓越的性能和易于维护的特点著称。它不仅适用于高响应需求的大型网站,也适合功能复杂的CMS管理系统,提供了一种全新的模板开发体验。 ... [详细]
  • Java 中的十进制样式 getZeroDigit()方法,示例 ... [详细]
  • 编码unicode解决了语言不通的问题.但是.unicode又有一个新问题.由于unicode是万国码.把所有国家的文字都编进去了.这就导致一个unicode占用的空间会很大.原来 ... [详细]
  • D17:C#设计模式之十六观察者模式(Observer Pattern)【行为型】
    一、引言今天是2017年11月份的最后一天,也就是2017年11月30日,利用今天再写一个模式,争取下个月(也就是12月份& ... [详细]
  • 深入理解线程池及其基本实现
    本文探讨了线程池的概念、优势及其在Java中的应用。通过实例分析不同类型的线程池,并指导如何构建一个简易的线程池。 ... [详细]
  • Hibernate全自动全映射ORM框架,旨在消除sql,是一个持久层的ORM框架1)、基础概念DAO(DataAccessorOb ... [详细]
  • 本文详细介绍了在Luat OS中如何实现C与Lua的混合编程,包括在C环境中运行Lua脚本、封装可被Lua调用的C语言库,以及C与Lua之间的数据交互方法。 ... [详细]
  • 本文深入探讨了WPF框架下的数据验证机制,包括内置验证规则的使用、自定义验证规则的实现方法、错误信息的有效展示策略以及验证时机的选择,旨在帮助开发者构建更加健壮和用户友好的应用程序。 ... [详细]
  • 二维码的实现与应用
    本文介绍了二维码的基本概念、分类及其优缺点,并详细描述了如何使用Java编程语言结合第三方库(如ZXing和qrcode.jar)来实现二维码的生成与解析。 ... [详细]
author-avatar
阳光碎了围脖_182
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有