前言:
现在你们对“悲观锁的实现方式java”大约比较注意,大家都需要了解一些“悲观锁的实现方式java”的相关资讯。那么小编同时在网摘上网罗了一些有关“悲观锁的实现方式java””的相关文章,希望看官们能喜欢,我们快快来学习一下吧!一、概念
乐观锁和悲观锁是两种思想,用于解决并发场景下的数据竞争问题。
乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。
二、实现方式
悲观锁的实现方式是加锁,加锁既可以是对代码块加锁(如Java的synchronized关键字),也可以是对数据加锁(如MySQL中的排它锁)。
乐观锁的实现方式主要有两种:CAS机制和版本号机制
1、CAS(Compare And Swap)
CAS操作包括了3个操作数:
需要读写的内存位置(V)进行比较的预期值(A)拟写入的新值(B)
CAS操作逻辑如下:如果内存位置V的值等于预期的A值,则将该位置更新为新值B,否则不进行任何操作。许多CAS的操作是自旋的:如果操作不成功,会一直重试,直到操作成功为止。
这里引出一个新的问题,既然CAS包含了Compare和Swap两个操作,它又如何保证原子性呢?答案是:CAS是由CPU支持的原子操作,其原子性是在硬件层面进行保证的。
2、版本号机制
除了CAS,版本号机制也可以用来实现乐观锁。版本号机制的基本思路是在数据中增加一个字段version,表示该数据的版本号,每当数据被修改,版本号加1。当某个线程查询数据时,将该数据的版本号一起查出来;当该线程更新数据时,判断当前版本号与之前读取的版本号是否一致,如果一致才进行操作。
三、优缺点和适用场景
1、功能限制
与悲观锁相比,乐观锁适用的场景受到了更多的限制,无论是CAS还是版本号机制。
例如,CAS只能保证单个变量操作的原子性,当涉及到多个变量时,CAS是无能为力的,而synchronized则可以通过对整个代码块加锁来处理。再比如版本号机制,如果query的时候是针对表1,而update的时候是针对表2,也很难通过简单的版本号来实现乐观锁。
2、竞争激烈程度
如果悲观锁和乐观锁都可以使用,那么选择就要考虑竞争的激烈程度:
当竞争不激烈 (出现并发冲突的概率小)时,乐观锁更有优势,因为悲观锁会锁住代码块或数据,其他线程无法同时访问,影响并发,而且加锁和释放锁都需要消耗额外的资源。当竞争激烈(出现并发冲突的概率大)时,悲观锁更有优势,因为乐观锁在执行更新时频繁失败,需要不断重试,浪费CPU资源。
四、项目使用实例
1、单例模式使用悲观锁:双重校验锁实现对象单例(线程安全)
public class Singleton {
private volatile static Singleton uniqueInstance ;
private Singleton () {
}
public static Singleton getUniqueInstance () {
// 先判断对象是否已经实例过,没有实例化过才进入加锁代码
if ( uniqueInstance == null ) {
// 类对象加锁
synchronized ( Singleton . class ) {
if ( uniqueInstance == null ) {
uniqueInstance = new Singleton ();
}
}
}
return uniqueInstance ;
}
}
另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。
uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分
为三步执行:
1. 为 uniqueInstance 分配内存空间
2. 初始化 uniqueInstance
3. 将 uniqueInstance 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2 。指令重排在单线程环境下不会出先问题,但是在
多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3 ,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance ,但此时 uniqueInstance 还未被 初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
2、乐观锁使用
sql中使用,例如springboot+mybatis操作数据库更新某个字段
update 'name' value ('xiaoming') where id =1
修改为 update 'name' value ('xiaoming') where id =1 and name='xiaohong'
此处修改则是CAS操作,防止数据被多次修改,先取出name值,再判断是否已被修改。
标签: #悲观锁的实现方式java