悲观锁和乐观锁是面试的高频问题
我们应该有一些概念
悲观锁顾名思义,就是悲观的认为只要不做正确的同步措施,他就一定会出现问题
乐观锁是说对于数据的同步我乐观的认为不采用同步措施也不会产生问题,但是如果产生了问题我就进行补救措施,比如retry
多线程间的同步机制主要有 四种
先不讲这四个的区别,只要先记住,Synchronized就是使用操作系统的互斥量
我的所有文章同步更新与Github-- Java-Notes ,想了解JVM(基本更完),HashMap源码分析,spring相关,,并发,剑指offer题解(Java版),可以点个star。可以看我的github主页,每天都在更新哟。
互斥只是同步机制的其中一个手段,也是很常见的保障并发正确性的手段
我们知道传统的锁(如synchronized或者reentrantLock)之所以被称为 重量级锁 ,就是因为他使用 操作系统互斥量 来实现同步
这是我们相对来说最熟悉的方式,一般新手学同步的时候,我们都是采用这个关键字,但是这个方式因为是使用 操作系统信号量 ,所以相对来说效率比较低
Java中 synchronized能实现同步基础: Java中的对象都可以作为锁
JVM 是基于 进入和退出 monitor对象 来实现方法同步和代码块同步
当synchronized关键字 经过编译 后,会在同步块的前后(同步代码块开始和结束或者异常的地方)分别形成 monitorenter
和 monitorexit
两个字节码指令。
同步方法规范中并没有明说,但是同步方法也可是使用上面两个指令来实现
在基本用法上,ReentrantLock和Synchronized很相似
相比Synchronized,ReentrantLock增加了如下功能:
在最新的版本中,对于Synchronized 的优化非常大,性能已经和ReentrantLock差不太多,所以如果不是使用那些高级功能,还是建议使用 synchronized。毕竟后面还是会大力优化Synchronized
下面是JDK8下,二者性能对比(前者是自增,后者是链表)
乐观锁不需要 线程挂起等待 ,所以也叫 非阻塞同步
一般在一个数据表中加一个 version字段,表示这个数据被更新的次数,当这个数据被 修改一次 ,版本号就 加一 。
举个例子
我现在银行账户有 10元,现在有一个version字段,版本号为 1.
现在我A操作取出2元,我先读入数据 version =1,然后扣除
与此同时,B操作也要取出1元,读入数据 version =1,然后扣除
这个时候,A操作完成,上传数据,版本号加一,version=2,这个版本大于当前的记录值 1,所以更新操作完成
这个时候,B操作也完成了,也要更新,他的版本号加一,version=2,然后更新的时候发现这个版本号和当前记录的版本号相同,不满足提交版本号必须大于当前记录的版本号的条件,不允许更新
这个时候,B操作就要重新读入再重复之前的步骤
通过这样的方法,我们就保证了B操作不会将A操作所更新的值覆盖,保证了数据的同步
CAS算法是基于硬件的发展产生的。为什么呢,因为我们需要这两个原子步骤
但是我们如何保证上面的两个步骤是安全的?肯定不能使用 互斥同步 ,如果使用了也不是非阻塞式了。这个时候我们就要依赖 硬件指令 了, 让看似很多步骤的命令,能够使用一条指令就能完成 ,比如:
前面的三条是很早之前就有的指令,后面的两条是现代的处理器才有的指令
CAS有三个数。
当且仅当V的值符合A的值的时候,处理器才会使用B值来更新V中的值;否则他就不执行更新。(上述比较和更新是一个原子操作)。一般情况下这个操作是 自旋的 ,也就是会不断尝试,直到完成