Java并发篇_乐观锁与悲观锁

乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。

一、引入概念

1、悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronizedReentrantLock等独占锁就是悲观锁思想的实现。

2、乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

3、两种锁的使用场景

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

二、乐观锁的实现方式——CAS算法

1、CAS算法

什么是CAS?

CAS全称为Compare And Swap即比较并交换,其算法公式如下:

函数公式:CAS(V,E,N)V:表示要更新的变量E:表示预期值N:表示新值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jpYccXsQ-1600049416983)(https://pics3.baidu.com/feed/810a19d8bc3eb135116a411f9e3739d6fc1f4497.jpeg?token=c57722e5bdc2b5d2e02a24affb2e2ac9&s=6AAC3C6203AEC5EF5CF530CE000080B1)]CAS

如果V值等于E值,则将V的值设为N。若V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。通俗的理解就是CAS操作需要我们提供一个期望值,当期望值与当前线程的变量值相同时,说明还没线程修改该值,当前线程可以进行修改,也就是执行CAS操作,但如果期望值与当前线程不符,则说明该值已被其他线程修改,此时不执行更新操作,但可以选择重新读取该变量再尝试再次修改该变量,也可以放弃操作。

2、“ABA问题”与“版本号机制”

CAS 会导致“ABA 问题”。CAS 算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。

比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中取出 A,并且two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。尽管线程 one 的 CAS 操作成功,但是不代表这个过程就是没有问题的。

部分乐观锁的实现是通过版本号(version)的方式来解决 ABA 问题,乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1 操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现 ABA 问题,因为版本号只会增加不会减少。

3、Java中的CAS

JDK5增加java.util.concurrent包,其中很多类使用了CAS操作。这些CAS操作基于Unsafe类中的native方法实现:

//第一个参数o为给定对象,offset为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值,
//expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作,
//设置成功返回true,否则返回false。
public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);

由于CAS作用的对象在主存里而不是在线程的高速缓存里,CAS操作在Java中需要配合volatile使用。

Java中的CAS主要包含以下几个问题:

  • ABA问题,即变量V初次读时是A值,被赋值时也是A值,但期间变量被赋值成B值,CAS会误认为他从没被修改过。AtomicStampedReference和AtomicMarckableReference类提供了监测ABA问题的能力,其中的compareAndSet方法首先检查当前引用是否等于预期引用,并且当前标志等于预期标志,全部相等则以原子方式将该引用和该标志的值设置为给定的更新值。
  • 循环开销,自旋CAS长时间不成功会给CPU带来非常大的执行开销。若JVM能支持pause命令,效率有一定提升。因为pause命令一方面可以延迟流水线执行命令,使CPU不会消耗过多的执行资源,另一方面可以避免退出循环时由内存顺序冲突引起的CPU流水线被冲突,从而提高CPU的执行效率。
  • 只能保证一个共享变量的原子操作,当操作涉及跨多个共享变量时CAS无效。可用AtomicReference封装多个字段来保证引用对象之间的原子性。

三、悲观锁——synchronized

1、synchronized

synchronized是Java中的关键字,是一种同步锁。可修饰实例方法,静态方法,代码块。

  • 修饰实例方法:对当前实例加锁,进入同步代码前要获得当前实例的锁
  • 修饰静态方法:对当前类对象加锁,进入同步代码前要获得当前类对象的锁
  • 修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

2、CAS与synchronized的使用情景

  • 简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),
  • synchronized适用于写比较多的情况下(多写场景,冲突一般较多)
  • 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
  • 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

补充: Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 “重量级锁” 。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 偏向锁 和 轻量级锁 以及其它各种优化之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/535683.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redhat与ubuntu配置网卡

redhat linux中设置网卡固定ip之前在xwindow下的redhat-config-network 设置网卡固定ip发现不起作用,设置好后就是ping不通。就查了些资料,更改 /etc/sysconfig/network-scripts/ifcfg-eth0(第一个网卡为eth0),配置dns的文件为 /etc/resolv.c…

SSH软件包:Sftp,scp和ssh-agent

这篇文章的中心是介绍在ssh软件包中非常有用的程序如:sftp,scp,ssh-agent,和ssh-add。在下文中我们假设sshd2守护进程很好地被设置并且运行良好。Sftp和scp总览让我们把注意力集中到sftp和scp上。第一个(sftp安全文件传…

JAVA并发篇_公平锁与非公平锁

简单的来说,如果一个线程组里,能保证每个线程都能拿到锁,那么这个锁就是公平锁。相反,如果保证不了每个线程都能拿到锁,也就是存在有线程饿死,那么这个锁就是非公平锁。 一、引入概念 1、公平锁&#xff1…

Java并发篇_进程线程

一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。…

Real提示“作为受限用户,您无足够的windows操作权限”的解决办法

运行Regedit.exe,翻到HKEY_CLASSES_ROOT/Software,删除Software;然后关闭注册表,再运行Regedit.exe,翻到HKEY_CLASSES_ROOT/Software,点右键选择“权限”,各个组都设置为“完全控制”和“读取”…

Java并发篇_线程详解

线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 一、线程的…

修改MYSQL最大连接数的3种方法

MYSQL数据库安装完成后,默认最大连接数是100,一般流量稍微大一点的论坛或网站这个连接数是远远不够的,增加默认MYSQL连接数的方法有两个 方法一:进入MYSQL安装目录 打开MYSQL配置文件 my.ini 或 my.cnf查找 max_connections100 …

可扩展的编程语言——Scala

一、Scala是什么 Scala是一种多范式的编程语言,其设计的初衷是要集成面向对象编程和函数式编程的各种特性。Scala运行于Java平台(Java虚拟机),并兼容现有的Java程序。 ​ Scala语言的名称来自于"可伸展的语言"。之所以…

ubuntu7.10 apache+php+mysql配置

本篇文章 经过许多次的测试和修改已经完成了在Ubuntu7.10 下 安装配置 ApachePHPMySQL的所有的工作. 1、在Ubuntu7.10 下安装 Apache2PHP5MySQL sudo apt-get install apache2 libapache2-mod-php5 php5 php5-gd mysql-server php5-mysql phpmyadmin在下载来自动安装配置的时候…

Spark-大规模数据处理计算引擎

官网:http://spark.apache.org 一、Spark是什么 Spark是一种快速、通用、可扩展的大数据分析引擎,2009年诞生于加州大学伯克利分校AMPLab,2010年开源,2013年6月成为Apache孵化项目,2014年2月成为Apache顶级项目。项目是…

MySQL Replace INTO的使用

REPLACE的运行与INSERT很相像。只有一点除外,如果表中的一个旧记录与一个用于PRIMARY KEY或一个UNIQUE索引的新记录具有相同的值,则在新记录被插入之前,旧记录被删除。请参见13.2.4节,“INSERT语法”。 注意,除非表有…

CentOS7下Spark集群的安装

从物理部署层面上来看,Spark主要分为两种类型的节点,Master节点和Worker节点,Master节点主要运行集群管理器的中心化部分,所承载的作用是分配Application到Worker节点,维护Worker节点,Driver,Ap…

Scala中class与object区别

calss scala编译器会字段帮我们生产一个私有字段和2个公有方法get和set scala 中没有 static 关键字,所以 对于一个class来说,所有的方法和成员变量在实例被 new 出来之前都是无法访问的 因此在class中的main方法没什么用了 scala 的object 中所有成员…

如何编写一个shell脚本

本文结合大量实例阐述如何编写一个shell脚本。 为什么要进行shell编程 在Linux系统中,虽然有各种各样的图形化接口工具,但是sell仍然是一个非常灵活的工具。Shell不仅仅是命令的收集,而且是一门非常棒的编程语言。您可以通过使用shell使大量的…

Scala变量和常用数据类型

一、 声明值和变量 Scala声明变量有两种方式,一个用val,一个用var。 声明方式:val / var 变量名 : 变量类型 变量值 val定义的值是不可变的,它不是一个常量,是不可变量,或称之为只读变量。 val示例&am…

ubuntu7.10下的vi用的怪怪的

到网上查了一下,原来是ubuntu7.10默认安装的是vim-tiny.可以重新安装vim-full #dpkg -l 如果是vim-tiny #apt-get install vim-full

(转)JVM监控工具介绍

2008年03月04日 16:57原作者: stone2083 原文地址:http://www.blogjava.net/stone2083/archive/2008/02/25/182081.htmljstatd启动jvm监控服务。它是一个基于rmi的应用,向远程机器提供本机jvm应用程序的信息。默认端口1099。实例:…

Scala的控制结构

一、 if else表达式 scala中没有三目运算符,因为根本不需要。scala中if else表达式是有返回值的,如果if或者else返回的类型不一样,就返回Any类型(所有类型的公共超类型)。 例如:if else返回类型一样 scal…

【Kubernetes】控制器Statefulset

Statefulset控制器 一、概念二、Statefulset资源清单文件编写技巧2.1、查看定义Statefulset资源需要的字段2.2、查看statefulset.spec字段如何定义2.3、查看statefulset的spec.template字段如何定义 三、Statefulset使用案例:部署web站点3.1、编写一个Statefulset资…

Scala 函数

scala定义函数的标准格式为: def 函数名(参数名1: 参数类型1, 参数名2: 参数类型2) : 返回类型 {函数体} 函数示例1:返回Unit类型的函数 def shout1(content: String) : Unit {println(content) }函数示例2:返回Unit类型的函数&#xff0…