Java悲观锁和乐观锁
悲观的并发策略——Synchronized互斥锁
互斥锁是最常见的同步手段,在并发过程中,当多条线程对同一个共享数据竞争时,它保证共享数据同一时刻只能被一条线程使用,其他线程只有等到锁释放后才能重新进行竞争。
synchronized就是一个典型的互斥锁,同时它也是一个JVM级别的锁,它的实现细节全部封装在JVM中实现,对开发人员只提供了synchronized关键词。根据锁的颗粒度,可以用synchronized对一个变量、一个方法、一个对象和一个类等加锁。被synchronized修饰的程序块经过编译后,会在前后生成monitorenter和monitorexit两个字节码指令,其中涉及到锁定和解锁对象的确定,这就要根据synchronized来确定了,假如明确指定了所对象,例如synchronized(变量)、synchronized(this)等,说明加解锁对象为变量或运行时对象。假如没有明确指定对象,则根据synchronized修饰的方法去找对应的锁对象,如修饰一个非静态方法表示此方法对应的对象为锁对象,如修饰一个静态方法则表示此方法对应的类对象为锁对象。当一个对象被锁住时,对象里面所有用synchronized修饰的方法都将产生堵塞,而对象里非synchronized修饰的方法可正常被调用,不收锁影响。
Synchronized还有另外一个重要的特性——可重入性。这个特性主要是针对当前线程而言的,可重入即是自己可以再次获得自己的内部锁,在尝试获取对象锁时,如果当前线程已经拥有了此对象的锁,则把锁的计数器加一,在释放锁时则对应地减一,当锁计数器为0时表示锁完全被释放,此时其他线程可对其加锁。可重入特性是为了解决自己锁死自己的情况,如下面伪代码:public class DeadLock{
public synchronized void method1(){}
public synchronized void method2(){
this.method1();
}
public static void main(String[] args){
DeadLock deadLock=new DeadLock();
deadLock.method2();
}
}
这种情况其实也并非不常见,一个类中的同步方法调用另一个同步方法,假如synchronized不支持重入,进入method2方法时当前线程将尝试获取deadLock对象的锁,而method2方法里面执行method1方法时,当前线程又要去尝试获取deadLock对象的锁,这时由于不支持重入,它要去等deadLock对象的锁释放,把自己阻塞了,这就是自己锁死自己的现象。所以重入机制的引入,杜绝了这种情况的发生。
synchronized实现的是一个非公平锁,非公平主要表现在获取锁的行为上,并非是按照申请锁的时间前后给等待线程分配锁的,每当锁被释放后,任何一个线程都有机会竞争到锁,这样做的目的是为了提高执行性能,当然也会产生线程饥饿现象。
synchronized最后一个特性(缺点)就是不可中断性,在所有等待的线程中,你们唯一能做的就是等,而实际情况可能是有些任务等了足够久了,我要取消此任务去干别的事情,此时synchronized是无法帮你实现的,它把所有实现机制都交给了JVM,提供了方便的同时也体现出了自己的局限性。
https://www.kancloud.cn/seaboat/java-concurrent/117869
乐观的并发策略——基于CAS的自旋
Synchronized互斥锁属于悲观锁,它有一个明显的缺点,它不管数据存不存在竞争都加锁,随着并发量增加,且如果锁的时间比较长,其性能开销将会变得很大。有没有办法解决这个问题?答案是基于冲突检测的乐观锁。这种模式下,已经没有所谓的锁概念了,每条线程都直接先去执行操作,计算完成后检测是否与其他线程存在共享数据竞争,如果没有则让此操作成功,如果存在共享数据竞争则可能不断地重新执行操作和检测,直到成功为止,可叫CAS自旋。
乐观锁的核心算法是CAS(Compareand Swap,比较并交换),它涉及到三个操作数:内存值、预期值、新值。当且仅当预期值和内存值相等时才将内存值修改为新值。这样处理的逻辑是,首先检查某块内存的值是否跟之前我读取时的一样,如不一样则表示期间此内存值已经被别的线程更改过,舍弃本次操作,否则说明期间没有其他线程对此内存值操作,可以把新值设置给此块内存。
CAS肯定是具有原子性的,不然就谈不上在并发中使用了,但这个原子性是由CPU硬件指令实现保证的,即使用JNI调用native方法调用由C++编写的硬件级别指令,jdk中提供了Unsafe类执行这些操作。另外,你可能想着CAS是通过互斥锁来实现原子性的,这样确实能实现,但用这种方式来保证原子性显示毫无意义。
CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作
1. ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
关于ABA问题参考文档: http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html
2. 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。3. 只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
在研究AQS框架时,会发现这个类很多地方都使用了CAS操作,在并发实现中CAS操作必须具备原子性,而且是硬件级别的原子性,java被隔离在硬件之上,明显力不从心,这时为了能直接操作操作系统层面,肯定要通过用C++编写的native本地方法来扩展实现。JDK提供了一个类来满足CAS的要求,sun.misc.Unsafe,从名字上可以大概知道它用于执行低级别、不安全的操作,AQS就是使用此类完成硬件级别的原子操作。
Unsafe是一个很强大的类,它可以分配内存、释放内存、可以定位对象某字段的位置、可以修改对象的字段值、可以使线程挂起、使线程恢复、可进行硬件级别原子的CAS操作等等,但平时我们没有这么特殊的需求去使用它,而且必须在受信任代码(一般由JVM指定)中调用此类,例如直接Unsafe unsafe = Unsafe.getUnsafe();获取一个Unsafe实例是不会成功的,因为这个类的安全性很重要,设计者对其进行了如下判断,它会检测调用它的类是否由启动类加载器Bootstrap ClassLoader(它的类加载器为null)加载,由此保证此类只能由JVM指定的类使用。
public static Unsafe getUnsafe() {
Class cc = sun.reflect.Reflection.getCallerClass(2);
if (cc.getClassLoader() != null)
throw new SecurityException(“Unsafe”);
return theUnsafe;
}
https://www.kancloud.cn/seaboat/java-concurrent/117870
http://blog.csdn.net/tangyongzhe/article/details/42084297
Java悲观锁和乐观锁的更多相关文章
- Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很 ...
- 通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其Java实现!
网上关于Java中锁的话题可以说资料相当丰富,但相关内容总感觉是一大串术语的罗列,让人云里雾里,读完就忘.本文希望能为Java新人做一篇通俗易懂的整合,旨在消除对各种各样锁的术语的恐惧感,对每种锁的底 ...
- java多线程----悲观锁与乐观锁
java多线程中悲观锁与乐观锁思想 一.悲观锁 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线 ...
- 写文章 通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其Java实现!
网上关于Java中锁的话题可以说资料相当丰富,但相关内容总感觉是一大串术语的罗列,让人云里雾里,读完就忘.本文希望能为Java新人做一篇通俗易懂的整合,旨在消除对各种各样锁的术语的恐惧感,对每种锁的底 ...
- Java中的锁-悲观锁、乐观锁,公平锁、非公平锁,互斥锁、读写锁
总览图 如果文中内容有错误,欢迎指出,谢谢. 悲观锁.乐观锁 悲观锁.乐观锁使用场景是针对数据库操作来说的,是一种锁机制. 悲观锁(Pessimistic Lock):顾名思义,就是很悲观,每次去拿数 ...
- java多线程系列3:悲观锁和乐观锁
1.悲观锁和乐观锁的基本概念 悲观锁: 总是认为当前想要获取的资源存在竞争(很悲观的想法),因此获取资源后会立刻加锁,于是其他线程想要获取该资源的时候就会一直阻塞直到能够获取到锁: 在传统的关系型数据 ...
- Java锁之乐观锁、悲观锁、自旋锁
java锁分为三大类乐观锁.悲观锁.自旋锁 乐观锁:乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别 ...
- Java 中的悲观锁和乐观锁的实现
一.定义 1.悲观锁:即很悲观,每次拿数据的时候都觉得数据会被人更改,所以拿数据的时候就把这条记录锁掉,这样别人就没法改这条数据了,一直到你的锁释放. 2.乐观锁:即很乐观,查询数据的时候总觉得不会有 ...
- 【Java】CAS的乐观锁实现之AtomicInteger源码分析
1. 悲观锁与乐观锁 我们都知道,cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换.切换涉及 ...
- Mysql锁机制--乐观锁 & 悲观锁
Mysql 系列文章主页 =============== 从 这篇 文章中,我们知道 Mysql 并发事务会引起更新丢失问题,解决办法是锁.所以本文将对锁(乐观锁.悲观锁)进行分析. 第一部分 悲观锁 ...
随机推荐
- shell 杂集
1.shell 相等比较注意 -eq 数字相等的比较 == 字符串相等的比较 2.== 和 = 的区别 == 可用于判断变量是否相等,= 除了可用于判断变量是否相等外,还可以表示赋值. = 与 == ...
- oracle11g安装与拆卸
Oracle 11g安装 1.解压下载的包,然后进入包内,点击setup.exe开始安装 . 2.出现如下:一般把那个小对勾取消,点击下一步进行, 弹出下图这个后点'是' 3.下图后,选择创建和配置数 ...
- 如何参加topcoder
1.注册账号 2.安装java运行环境 3.下载客户端 4.提示:应用程序已被java安全阻止:控制面板里找到java,将topcoder.com添加到安全列表 5.运行客户端
- js 找数组中的最值
背景: 2个数组以下 , 比如 [[4, 9, 1, 3], [13, 35, 18, 26], [32, 35, 97, 39], [1000000, 1001, 857, 1]] 找最值的时候, ...
- 豆瓣项目(用react+webpack)
用豆瓣电影api的项目 电影列表组件渲染 步骤: 1. 发送Ajax请求 1.1 在组件的componentWillMount这个生命周期钩子中发送请求 1.2 发送ajax XMLHttpReque ...
- idea下的jsp开发中cannot resolve taglib with uri的解决方法
写jsp难免会用到<c:foreach>标签,于是我在idea上的jsp顶头写下了 <%@ taglib prefix="c" uri= 'http://java ...
- spring之pom.xml配置
spring之pom.xml配置 <?xml version="1.0" encoding="UTF-8"?> <project xmlns= ...
- 三.Windows I/O模型之事件选择(WSAEventSelect )模型
1.事件选择模型:和异步选择模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知.对于异步选择模型采用的网络事件来说,它们均可原封不动地移植到事件选择模型.事件选择模型和 ...
- 【Five-Minute Share】“为什么要选择自增型的主键”
我们在开发的时候经常会听到这样的建议:1. 设计数据库表的时候,要为每个表设置一个主键:2. 主键最好是跟业务无关的: 3. 最好是自增的: 于是,很多新入行的程序猿们把这些前辈们的教条拿来就用,每个 ...
- 洛谷P2296 寻找道路_简单BFS
Code: #include<cstdio> #include<queue> #include<algorithm> using namespace std; co ...