系列文章目录
第一章 java JUC并发编程 Future: link
第二章 java JUC并发编程 多线程锁: link
第三章 java JUC并发编程 中断机制: link
第四章 java JUC并发编程 java内存模型JMM: link
第五章 java JUC并发编程 volatile与JMM: link
第六章 java JUC并发编程 CAS: link
文章目录
- 系列文章目录
- 1 概述
- 1.1 没有CAS之前多线程环境不使用原子类保证线程安全i++(基本数据类型)
- 1.2 使用cas之后多线程环境使用原子类保证线程安全i++
- 2 CAS是什么
- 2.1 CAS原理
- 2.1.1 例
- 2.1.2 unasfe类
- 2.1.3 硬件级别保证
- 2.1.4 UnSafe的理解
- 2.1.5 UnSafe底层原理
- 2.1.5.1 getAndIncrement()
- 2.1.5.2 native修饰的方法代表是底层方法
- 2.1.5.3 compxchg
- 2.1.5.4 在不通的操作系统下会调用不同的cmpxchg重载函数
- 2.2 总结
- 3 原子引用
- 3.1 Atomiclnteger<T> 其他原子类型
- 4 CAS与自旋锁
- 4.1 自己实现自旋锁
- 5 CAS的缺点
- 5.1 循环时间长导致cpu开销过大
- 5.2 引出ABA问题
- 5.2.1 AtomicStampedReference简单case
- 5.2.2 AtomicInteger BAB问题复现
- 5.2.3 AtomicStampedReference 版本号机制避免ABA
1 概述
了解原子类:java.util.concurrent.atomic 下面所有相关的类和api的调用
1.1 没有CAS之前多线程环境不使用原子类保证线程安全i++(基本数据类型)
1.2 使用cas之后多线程环境使用原子类保证线程安全i++
两者的区别AtomicInteger要比加了synchronized的重量级锁性能更好。
类似乐观锁
2 CAS是什么
2.1 CAS原理
2.1.1 例
package com.atguigu.springcloud.util.interrup;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
//预期是5的时候才替换成2022
System.out.println(atomicInteger.compareAndSet(5,2022)+" :"+ atomicInteger.get());
}
}
2.1.2 unasfe类
this:当前对象
valueoffset:当前对象内存地址的偏移量
expect:期望值
update:更新值
2.1.3 硬件级别保证
AtomicInteger底层调用的是Unsafe类
2.1.4 UnSafe的理解
i++是线程不安全的,那atomiclnteger.getAndIncrement()就可以么
var1:this(当前对象)
var2:valueoffset
var5:当前对象最新值
如果对比成功让var5+var4(1)
2.1.5 UnSafe底层原理
2.1.5.1 getAndIncrement()
2.1.5.2 native修饰的方法代表是底层方法
2.1.5.3 compxchg
2.1.5.4 在不通的操作系统下会调用不同的cmpxchg重载函数
2.2 总结
CAS是靠硬件实现的从而在硬件层面提升效率,最底层还是交给硬件来保证原子性和可见性实现方式是基于硬件平台的汇编指令,在intel的CPU中(X86机器上),使用的是汇编指令cmpxchg指令。
核心思想是:比较要更新变量的值V和预期值E(compare),相等才会将V的值设为新值N(swap)如果不相等自旋再来。
3 原子引用
3.1 Atomiclnteger 其他原子类型
package com.atguigu.springcloud.util.interrup;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import java.util.concurrent.atomic.AtomicReference;
@Getter
@ToString
@AllArgsConstructor
class User{
String userName;
int age;
}
public class AtomicReferenceDemo {
public static void main(String[] args) {
AtomicReference<User> atomicReference = new AtomicReference<>();
User z3 = new User("z3",22);
User li4 = new User("li4",28);
atomicReference.set(z3);
System.out.println(atomicReference.compareAndSet(z3,li4)+":"+atomicReference.get().toString());
}
}
4 CAS与自旋锁
4.1 自己实现自旋锁
package com.atguigu.springcloud.util.interrup;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"come in");
while (!atomicReference.compareAndSet(null,thread)){}
}
public void myUnlock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+" invoked myUnLock()");
}
public static void main(String[] args) {
//自己实现的自旋锁
/**
* 实现一个自旋锁
* 自旋锁好处:循环比较获取没有类似wait的阻塞
*
* 通过cas操作来完成自旋锁,A线程先进来调用myLock方法自己持有锁5分钟,
* B随后进来发现当前所有线程持有锁,所以只能通过自旋等待,直到A释放锁后B随后抢到。
*/
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
System.out.println(Thread.currentThread().getName()+"正在干活");
try{
TimeUnit.SECONDS.sleep(10);}catch (InterruptedException e){e.printStackTrace();}
spinLockDemo.myUnlock();
},"AA").start();
try{TimeUnit.SECONDS.sleep(2);}catch (InterruptedException e){e.printStackTrace();}
new Thread(()->{
spinLockDemo.myLock();
System.out.println(Thread.currentThread().getName()+"正在干活");
spinLockDemo.myUnlock();
},"BB").start();
}
}
5 CAS的缺点
5.1 循环时间长导致cpu开销过大
5.2 引出ABA问题
解决:版本号时间戳原子引用
5.2.1 AtomicStampedReference简单case
package com.atguigu.springcloud.util.interrup;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.concurrent.atomic.AtomicStampedReference;
@NoArgsConstructor
@AllArgsConstructor
@Data
class Book{
private int id;
private String bookName;
}
public class AtomicStampedDemo {
public static void main(String[] args) {
Book javaBook = new Book(1,"javaBook");
AtomicStampedReference<Book> stampedReference = new AtomicStampedReference<>(javaBook,1);
System.out.println(stampedReference.getReference()+":"+stampedReference.getStamp());
Book mysqlBook = new Book(2,"mysqlBook");
//参数1:期望book 参数2:替换book 参数3:期望版本号 参数4:替换版本号
boolean b;
b = stampedReference.compareAndSet(javaBook,mysqlBook,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println(b+"\t"+stampedReference.getReference()+":"+stampedReference.getStamp());
//再换回去,增加了版本号的检查
b = stampedReference.compareAndSet(mysqlBook,javaBook,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println(b+"\t"+stampedReference.getReference()+":"+stampedReference.getStamp());
}
}
5.2.2 AtomicInteger BAB问题复现
package com.atguigu.springcloud.util.interrup;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 多线程下ABA问题
*/
public class ABADemo {
static AtomicInteger atomicInteger = new AtomicInteger(100);
public static void main(String[] args) {
new Thread(()->{
//改为101
atomicInteger.compareAndSet(100,101);
try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
//再改回100 模拟ABA
atomicInteger.compareAndSet(101,100);
},"t1").start();
//只检查内容忽略了B的操作
new Thread(()->{
try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(atomicInteger.compareAndSet(100,202)+"\t"+atomicInteger.get());
},"t2").start();
}
}
5.2.3 AtomicStampedReference 版本号机制避免ABA
package com.atguigu.springcloud.util.interrup;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* 多线程下ABA问题
*/
public class ABADemo {
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t"+"首次版本号:"+stamp);
//保证后面的t4线程拿到的版本号和这里一样
try {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t"+"2次版本号:"+ atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t"+"3次版本号:"+ atomicStampedReference.getStamp());
},"t3").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t"+"首次版本号:"+stamp);
//等待t3发生了ABA问题
try {TimeUnit.MILLISECONDS.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
boolean b= atomicStampedReference.compareAndSet(100,2022,stamp,stamp+1);
System.out.println(b+"\t"+atomicStampedReference.getReference()+"\t"+atomicStampedReference.getStamp());
},"t4").start();
}
}
结果: