乐观锁和悲观锁,java中解决乐观锁经典ABA问题

出现背景:

在需要提高程序的并发量的时候就需要使用多线程,但是多线程中有时会有线程不安全的问题,使用锁的话,必然会降低程序的执行效率。

使用场景:

在一些场景下线程不安全出现的频率较小,特别是我们读数据的时候比较多,修改数据的时候比较少,这个时候就可以使用乐观锁来解决。

传统的就是不管会不会出现线程安全,直接带上锁,也就是悲观锁。在写数据多的场景,使用悲观锁要好一点,不管三七二十一,直接synchronized或者ReentrantLock管上。

数据库中乐观锁的运用:

在数据库中其实也有乐观锁的使用,以mysql举例,创建一个表的时候可以在表的字段中加一个version,每次要修改该字段的时候去比较version的版本开始读的版本是否一致,不一致就不能修改,一致才能修改,修改后让version自增。就可以达到在mysql设置隔离级别为SERIALIZABLE(串行化:执行该行数据的时候,会将该行的加锁,执行完了之后才能释放锁)般的效果。

乐观锁中比较经典的问题就是ABA问题,也就是当多个线程运行的时候:(面试要问)

  • 线程一:修改值A为B
  • 线程二:修改值A为B之后又将B修改为A。

如果高并发的时候,线程一开始执行,还没有执行完,线程二已经执行完了,线程一去判断值是否为A,就会认为这个值没有被修改过,就会去修改这个值。出现线程不安全。

jdk1.5之后提供了通过AtomicStampedReference类(使用的CAS算法实现的)来解决这个问题,原理同上面讲的version。只要修改了就让version版本自增。下面是测试代码:

package com.yellow.cas;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABATest3 {

    public static void main(String[] args) throws InterruptedException {
        //创建一个原子Integer类型值为100
        //final AtomicInteger atomicInteger = new AtomicInteger(100);
        //为了解决ABA问题使用jdk提供的 AtomicStampedReference
        final AtomicStampedReference<Integer> reference = new AtomicStampedReference<Integer>(100, 0);

        //创建一个线程t1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //进入线程获取版本号
                int version = reference.getStamp();
                try {
                    //休眠1秒,让t2线程先执行
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //改变Integer的值为200,并打印修改值是否成功
                boolean flag = reference.compareAndSet(100, 200, version, reference.getStamp() + 1);
                System.out.println("t1:" + flag);
            }
        });
        //创建一个线程t2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {

                //改变Integer的值为300,并打印修改值是否成功
                reference.compareAndSet(100, 101, reference.getStamp(), reference.getStamp() + 1);
                boolean flag = reference.compareAndSet(101, 100, reference.getStamp(), reference.getStamp() + 1);
                System.out.println("t2:" + flag);
            }
        });

        t1.start();
        t2.start();
//        让t2先执行,模拟高并发下t2线程先执行
        t2.join();

    }
}

运行结果,t2线程修改成功了。t1线程去获取版本号对比,发现不一致就修改失败

 

 

注意:这里的Integer要修改超过-128到127范围,超过了这个范围,自动装箱就会给你产生一个新的Integer对象

去判断的时候就会就都返回flase;

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值