出现背景:
在需要提高程序的并发量的时候就需要使用多线程,但是多线程中有时会有线程不安全的问题,使用锁的话,必然会降低程序的执行效率。
使用场景:
在一些场景下线程不安全出现的频率较小,特别是我们读数据的时候比较多,修改数据的时候比较少,这个时候就可以使用乐观锁来解决。
传统的就是不管会不会出现线程安全,直接带上锁,也就是悲观锁。在写数据多的场景,使用悲观锁要好一点,不管三七二十一,直接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;