CAS比较并交换
简介
如果线程的期望值和主物理内存的值一样,便将值写入主物理内存,如果不同,本次修改失败,并重新获取值。
特点
不加锁,并发性强。
底层实现
汇编:lock cmpxchg
使用场景
AtomicInteger等类底层就是CAS实现
//AtmoicInteger
public final int getAndIncrement(){
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//unsafe.getAndAddInt
//var2内存偏移
public final int getAndAddInt( Object var1, long var2 , int var4){
int var5;
do {
var5 = this.getIntVolatile(var1,var2);
//如果之前获取的var5在主内存中没被修改,则将新增写入,如果已经修改,则再次读取var5,直到成功写入内存
}while( !this.compareAndSwapInt( var1, var2, var5, var5+var4));
return var5;
}
CAS缺点
1、如果CAS一直不成功,循环时间可能长,开销大。
2、只能保证一个变量的原子操作(多个变量需要加锁)
3、引起ABA问题
线程一和线程二都把A放到自己的工作空间,但二号线程快,它把A改成B并放回主物理内存,一段时间后又把B改成A放回主物理内存。此时线程一回来,虽然期望值和实际值一样,但中间可能被改过很多次。
在解决ABA问题前先了解什么是原子引用
public class Main{
@Getter
@ToString
@AllArgsConstructor
static class User{
String name;
int age;
}
public static void main( String[] args){
User a = new User("a",10);
User b = newUser("b",20);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set( a );//线程安全
atomicReference.compareAndSet(a , b);//线程安全,如果当前是a,就将其改为b,否则不改
}
}
ABA解决方法:加时间戳的原子引用
public class Main{
static AtomicReference<Integer> atomicReference = newAtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args ){
System.out.println("===========产生ABA==========");
new Thread( () -> {
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"t1").start();
newThread( () -> {
try{ TimeUnit.SECONDS.sleep(1); }catch( InterruptedException e ){ e.printStackTrace(); }
System.out.println( Thread.currentThread().getName()+"修改成功吗?"+atomicReference.compareAndSet(100,2021)+"\t"+atomicReference.get());
},"t2").start();
try{ TimeUnit.SECONDS.sleep(3); }catch( InterruptedException e ){e.printStackTrace();}
System.out.println("=============ABA问题解决=============");
new Thread( () -> {
int stamp = atomicStampedReference.getStamp();
try{ TimeUnit.SECONDS.sleep(1); }catch( InterruptedException e ){e.printStackTrace();}
atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"完成,现在版本号:"+atomicStampedReference.getStamp());
},"t3").start();
new Thread( () -> {
int stamp = atomicStampedReference.getStamp();
try{ TimeUnit.SECONDS.sleep(3); }catch(InterruptedExceptione){e.printStackTrace();}
System.out.println(Thread.currentThread().getName()+"修改成功吗?"+atomicStampedReference.compareAndSet(100,101,stamp,stamp+1));
System.out.println("原版本号"+stamp+"\t当前最新版本号"+atomicStampedReference.getStamp());
},"t4").start();
}
}
运行结果
=产生ABA
t2修改成功吗?true 2021
=ABA问题解决=
t3完成,现在版本号:3
t4修改成功吗?false
原版本号1 当前最新版本号3
手写自旋锁
public class SpinLock{
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock(){
Thread thread = Thread.currentThread();
while( !atomicReference.compareAndSet(null,thread) );
System.out.println(Thread.currentThread().getName()+"获取锁");
}
public void unlock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"释放锁");
}
public static void main(String[]args){
SpinLock lock = new SpinLock();
for(int i=0 ; i<5 ; i++){
new Thread(()->{
lock.lock();
try{TimeUnit.SECONDS.sleep(2);}catch(InterruptedExceptione){e.printStackTrace();}
lock.unlock();
},String.valueOf(i)).start();
}
}
}
运行结果:
1获取锁
1释放锁
0获取锁
0释放锁
2获取锁
2释放锁
4获取锁
4释放锁
3获取锁
3释放锁
其他Java锁的介绍
https://blog.csdn.net/D1124615130/article/details/116160604