java锁分为三大类乐观锁、悲观锁、自旋锁

乐观锁:乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。java 中的乐观锁基本都是通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败

悲观锁:悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。java中的悲观锁就是Synchronized,AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock

自旋锁:自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。线程自旋是需要消耗 cup 的,说白了就是让 cup 在做无用功,如果一直获取不到锁,那线程也不能一直占用 cup 自旋做无用功,所以需要设定一个自旋等待的最大时间。如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。

自旋锁的优缺点:

自旋锁尽可能的减少线程的阻塞,这对于锁竞争不激烈,且占用锁时间非常短的代码块来说性能大幅度提升,因为自旋锁的消耗会小于线程阻塞挂起在唤醒的消耗,这些操作会导致线程发生两次线程上下文切换。

但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用 cpu 做无用功,占着 XX 不 XX,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要 cup 的线程又不能获取到 cpu,造成 cpu 的浪费。所以这种情况下我们要关闭自旋锁;

自旋锁时间阈值(1.6 引入了适应性自旋锁)

自旋锁目的是为了占着cpu的资源不释放,等到获取到锁立即进行处理,但是如何去选择自旋锁的执行时间?如果自旋时间太长,会有大量的线程处于自旋状态占用cpu资源,进而会影响整个系统的性能,因为自旋周期很重要 - -~。

Jvm对于自旋周期在1.5的时候是写死的在1.6的时候后引入了适应性自旋锁,适应性自旋锁意味着自旋的时间不在是固定的了,而是由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定,基本认为一个线程上下文切换的时间是最佳的一个时间,同时 JVM 还针对当前 CPU 的负荷情况做了较多的优化,如果平均负载小于 CPUs 则一直自旋,如果有超过(CPUs/2)
个线程正在自旋,则后来线程直接阻塞,如果正在自旋的线程发现 Owner 发生了变化则延迟自旋时间(自旋计数)或进入阻塞,如果 CPU 处于节电模式则停止自旋,自旋时间的最坏情况是 CPU的存储延迟(CPU A 存储了一个数据,到 CPU B 得知这个数据直接的时间差),自旋时会适当放弃线程优先级之间的差异。

自旋锁的开启
JDK1.6 中-XX:+UseSpinning 开启;-XX:PreBlockSpin=10 为自旋次数;JDK1.7 后,去掉此参数,由 jvm 控制;

CAS算法是什么

CAS(比较与交换,Compare and swap) 是一种有名的无锁算法(后面会提到什么是无锁(lock-free))。CAS是一种CPU级别的指令,在大多数处理器架构,包括IA32、Space中采用的都是CAS指令。顾名思义这个算法分为两部分,比较和交换,一共有三个数据,A数据,B数据,C数据

A数据是内存的最新值,是volatile类型的,对于所有线程都是可见的
B是相当于是A的快照数据(一份缓存)
C是A期望的值,也就是结果值

开始有两个线程,同时需要修改内存中的同一个变量S,两个变量要同时对S的值加一
正常情况下,第一个线程拿到内存中的最新值放到A(对于所有线程是可见的)中,同时将这个最新值打一个快照将值放到B中,然后对B中的值进行加一操作结果放到C中,判断A中的值和B中的值是否相等,这时A的值和B的值是相等的,将C的值放到A中,其他线程实时看到A的最新值,第二个线程在第一个线程之后执行相同的操作没什么问题。
上图理解一下:

但是在并发情况下,第一个线程拿到内存中的最新值放到A中,第二个线程同样拿到内存中的罪行值放到A中,两个线程同时将这个最新值打一个快照将值放到B中,然后对B中的值进行加一操作结果放到C中,线程一判断A中的值和B中的值是否相等,这时A的值和B的值是相等的,将C的值放到A中,让其他线程实时看到A的最新值,但是这个时候线程二发现A的值已经和B的值不相等了,那么线程二中之前的操作全部作废,重新再来一遍,线程二再次拿到最新的值放到A中,同时将快照放到B中,加完一之后的值放到C中,判断A和B的值,此时没有人修改内存中的值,A和B的值相等,将C的值放到A中,修改内存中的变量。

上图理解一下:

通过上面的比较和交换,附加回滚操作,使得多线程之间的并发问题得以解决。
当通过以上方式理解透彻了之后,就可以明白实际上CAS操作是通过对内存中的值缓存(B),然后将操作好的数据缓存(C),判断内存中最新的值是否与自身缓存的值(B)相等,如果相等说明自己缓存的是最新的值,如果不相等,说明自己缓存的值已经是过期的数据,需要重新执行一遍刚才的操作,这就是CAS算法(是不是很简单,虽然这样说很简单,但是在实际使用过程中用起来还是比较麻烦的)。

实际上java.util.concurrent.atomic中的AtomicXXX,都使用了这些类似的算法保证了原子操作保证了多线程下并发的问题。

CAS算法的性质

通过对CAS算法的理解再理解CAS的性质就非常的容易了
1.属于乐观锁,说到乐观锁就得提一下锁的概念了,锁就是两个人同时上厕所,但是厕所只有一个,第一个人上厕所把门锁住了,第二个人就不能上,这个门锁就是我们程序中所谓的锁。

在这个基础上又产生出了乐观锁和悲观锁,还拿上面的例子举例,悲观锁就是,第一个人上厕所的时候,第二个人只能在外面等待,只能憋着什么都不做;而乐观锁就是,第一个人上厕所的时候,第二个人不断的敲门问里面的人是否上完了,这就是乐观锁。总结一下就是悲观锁悲观的认为共享的东西是有并发的,需要将共享的东西锁住,而乐观锁就是乐观的认为共享的东西是没有并发的,只有我一个人进行修改,只不过是失败了就进行不断的尝试,直至达到预期,说白了就是不断的进行想要达到效果的操作。

2.非阻塞的,因为在不断的尝试当中是没有线程被阻塞的,所有的线程都在轮循自己的任务,没有线程在进行等待,所以是非阻塞的。

3.无锁(lock-free),就是没有锁进行控制,所有的线程都执行自己的任务,都去尝试执行自己的操作。
说到无锁就不得不提wait-free,可以看到lock-free下还是有线程有可能在一直循环,导致死循环而不能退出,但是wait-free是保证可以在有限的循环内必定会结束,也就是在lock-free死循环尝试的情况下可以在有限的步骤内执行完毕(可能是10,100,1000…)。这是我的理解,而且现在关于wait-free的算法或者实现不是很多,或者wait-free的效率还不如lock-free的效率,因为wait-free虽然说是有限的步骤,但是这个有限的步骤可能非常大,导致执行起来反倒会慢,所以我也没做过多的研究。

ABA问题

其实ABA问题很简单,就是有两个线程同时修改共享变量,线程一获取到内存最新值为1,快照值也为1,想要修改为2,这时线程二也获取到最新值为1,快照为1,想要修改为2,线程二执行完成之后,将内存改为2,之后线程二又要执行一个操作,想要将值减1,这时线程二有获取最新值为2,快照值为2,想要修改为1,执行完毕后将内存最新的值修改为1,这时线程一发现内存最新值和快照值相等,将内存值修改为2。可以发现线程二执行减1的操作对于线程一来说丢失了,此时就会产生问题,达不到我们预期想要达到的效果。

可能有的人可能要问了,线程一想要进行加1的操作,线程二想要加1后减1,最后的结果不应该是2么,应该没什么问题,但是如果线程一和线程二修改的是引用中的内容呢,线程二已经将引用修改了,但是线程一感知不到,还要去修改原来引用中的内容,这时就可能导致访问野指针,进而访问不到数据。实际上产生这个问题的原因就是在进行CAS的过程中无法判断这个变量是否是被别人进行了加一减一的操作,从而导致判断失误。
所以在解决此类问题的时候会在获取和赋值的时候给变量加一个版本号的东西,这时候线程一再进行判断的时候发现虽然值相同但是版本号不同也会去重新获取最新数据进行CAS操作。

Java锁之乐观锁、悲观锁、自旋锁的更多相关文章

  1. Java多线程:乐观锁、悲观锁、自旋锁

    悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁.传统的关系型数据 ...

  2. Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等

    Java 中15种锁的介绍 Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等,在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类 ...

  3. 二、多线程基础-乐观锁_悲观锁_重入锁_读写锁_CAS无锁机制_自旋锁

    1.10乐观锁_悲观锁_重入锁_读写锁_CAS无锁机制_自旋锁1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将 比较-设置 ...

  4. Optimistic concurrency control 死锁 悲观锁 乐观锁 自旋锁

    Optimistic concurrency control https://en.wikipedia.org/wiki/Optimistic_concurrency_control Optimist ...

  5. Java锁之自旋锁详解

    锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) .这些已经写好提供的锁为我们开发提供了便利,但是锁的具体性质以及类 ...

  6. Java 多线程之自旋锁

    一.什么是自旋锁? 自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环. 获取锁的线 ...

  7. java锁的种类以及辨析(一):自旋锁

    作者:山鸡 锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) .这些已经写好提供的锁为我们开发提供了便利,但是锁的具 ...

  8. 轻松搞懂Java中的自旋锁

    前言 在之前的文章<一文彻底搞懂面试中常问的各种“锁”>中介绍了Java中的各种“锁”,可能对于不是很了解这些概念的同学来说会觉得有点绕,所以我决定拆分出来,逐步详细的介绍一下这些锁的来龙 ...

  9. 深入理解java虚拟机-第13章-线程安全与锁优化

    第十三章 线程安全与锁优化 线程安全 java语言中的线程安全 1 不可变.Immutable 的对象一定是线程安全的 2 绝对线程安全 一个类要达到不管运行时环境如何,调用者都不需要额外的同步措施, ...

  10. 多线程之美6一CAS与自旋锁

    1.什么是CAS CAS 即 compare and swap 比较并交换, 涉及到三个参数,内存值V, 预期值A, 要更新为的值B, 拿着预期值A与内存值V比较,相等则符合预期,将内存值V更新为B, ...

随机推荐

  1. MXNet源码分析 | Gluon接口分布式训练流程

    本文主要基于MXNet1.6.0版本,对Gluon接口的分布式训练过程进行简要分析. 众所周知,KVStore负责MXNet分布式训练过程中参数的同步,那么它究竟是如何应用在训练中的呢?下面我们将从G ...

  2. 《手把手教你》系列技巧篇(六十八)-java+ selenium自动化测试 - 读写excel文件 - 下篇(详细教程)

    1.简介 今天继续操作Excle,小伙伴或者童鞋们是不是觉得宏哥会介绍第三种工具操作Excle,今天不介绍了,有两种就够用了,其实一种就够用了,今天主要是来介绍如何使用不同的数据类型读取Excel文件 ...

  3. Java IO模型:BIO、NIO、AIO

    Java IO模型:BIO.NIO.AIO 本来是打算直接学习网络框架Netty的,但是先补充了一下自己对Java 几种IO模型的学习和理解.分别是 BIO.NIO.AIO三种IO模型. IO模型的基 ...

  4. 创建jsp页面出现The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path错误

    原因未添加tomcat服务器 第一步: 第二步:

  5. [Python]从哪里开始学习写代码(未完待续)

    预警:这只是我在学习中的一点感受,可能并不完全准确,也不包括面向对象编程的思想(我还不太懂),也有水文的嫌疑,大佬请温和批评指正或者绕道. 计算机语言 语言,是用来交流的.计算机是不能直接听懂人的语言 ...

  6. Renix软件如何发送CRC错误的报文——网络测试仪实操

    我们在日常使用Renix软件时,有时候需要发送CRC错误的报文,那么如何操作呢?接下来为你详细介绍一下操作步骤. 1.打开Renix软件,连接机框并预约测试端口: 2.添加流模板 3.给P1设置CRC ...

  7. Shell、命令行界面、控制台什么区别

    Shell 是什么?Shell 是一个命令解释器,它为用户提供了一个向 操作系统内核发送请求以便运行程序界面系统级程序,它的作用就是遵循一定的语法将输入的命令加以解释并传给系统,他大意是指对系统的操控 ...

  8. Oracle数据库的一些常用命令

    转至:https://blog.csdn.net/qq_36843413/article/details/81409152?utm_medium=distribute.pc_relevant_t0.n ...

  9. linux多进/线程编程(7)——多线程1(线程的创建,回收,分离,设置线程属性等)

    参考资料: 1.博客1:https://blog.csdn.net/zhou1021jian/article/details/71531699 2.博客2:https://blog.csdn.net/ ...

  10. c/c++ 常见字符串处理函数总结 strlen/sizeof strcpy/memcpy/strncpy strcat/strncat strcmp/strncmp sprintf/sscanf strtok/split/getline atoi/atof/atol

    这里总结工作中经常用到的一些c/c++的字符串处理方法,标黑的是使用频率较高的   1.strlen函数:计算目标字符串长度,    格式:strlen(字符指针指向区域) 注意1:①不包含字符串结束 ...