1.讲解CAS概念
CAS使用了乐观锁的概念,实现了原子操作那么什么是原子操作,其实可以理解为一个事务,下图表示
CAS内部执行原理:
传入旧值跟新值让compare去比较内存中已经存在的旧值,如果传入进来的旧值跟内存中的旧值一致那就把传入进来的新增修改,如果不相等则采用自旋的方式拿到内存中的旧值在再次进行比较,自旋可以可以理解为自旋锁机制含义
那这里小伙伴们肯定会产生疑问了这cas不是有3步独立的指令了吗,
指令1拿到:内存地址的值
指令2拿到:我传入进来的旧值
指令3拿到:我传入进来的新值
那多线程并法会不会导致我指令重排掉呢,这一点小伙伴们可以不必担心因为CPU处理器以及为我们提供了专门的CAS指令,可以保证指令顺序执行并将原子的更新值
CAS存在的问题:
ABA问题:线程A更新值时比较的旧值为3,由于并发速度太快了现在线程B在线程A更新值时又把数据更新为3,此时线程A更新成功。就比喻小明接满一杯水玩去了,小狗偷偷摸摸的过来把水喝了一半又把他接满了,此时小明回来了咦发现水还是满的小明就喝了
解决: 可以采用AtomicMarkableReference,AtomicStampedReference进控制
开销问题: 由于存在并发情况同时修改值,如果值被改过了,就会重新获取内存中的值进行比较,在此期间如果一直修改不成功,会导致做死的循环,会有性能损耗
解决: 没必要解决总会成功的,不管他
只能保证一个共享变量的原子操作: 每次更新操作我只能更新一个值,即一个CAS指令,如果有连个CAS指令那就时独立的了不能保证原子操作
解决: 可以通过AtomicReference定义一个对象(对象里面包含多个更新的字段),即可解决这个问题
既然谈到了锁那就给小伙伴们讲几个java中常用的锁吧
锁:单线程下锁是没有意义的,就是没有锁,锁的话是多线程下的,锁其实说的更加简单一点就是一个事务,一个保证线程之间共享资源的一种安全机制
一个锁里面可以包含其他的锁,比如同步锁里面有(偏向锁,独占锁,重入锁等等)
锁粒度:就是锁的级别,锁住的资源越小发生死锁的几率也就越小,比喻我家,我的房间每次只允许一个人进入那就锁住我的房间就好了,没必要把我家锁了,一次只能进入一个人那比如爸回家了不可能让他在外面等着吧
锁开销:一个锁的占用内存跟所需执行完毕需要的时间,就是锁的性能开销
锁竞争:线程相互排斥对方,竞争一个锁资源,锁的粒度越小发生的竞争越就越小
偏向锁:也可以称为单线程记录锁,比喻线程A拿到了锁然后操作完了准备退出了,但是不会释放锁资源,此时线程A又来请求锁,此时会判断锁的话是不是线程A的如果是直接用就好了,如果不是是线程B的那就会释放掉线程A的锁资源,让线程B拿到锁资源,那此时锁就是线程B的了,此时其他的线程要来请求锁也是跟线程B去拿线程A的锁资源操作是一样的
重入锁:也可以称为递归锁,比喻线程A进入了work对象同步方法A锁住的是当前的work对象,然后work对象同步方法A内部调用work对象同步方法B,方法A跟方法B都是锁住当前work对象,这是可以调用成功的,因为实现了重入锁,如果没实现会死锁的,因为方法A已经持有work对象锁资源再去访问持有当前work对象锁资源是会阻塞的,只能访问线程A的锁资源方法不能访问其他线程已经获取锁资源的方法
自旋锁:也可以称为循环比较锁,比喻CAS原子操作如果出现数据被别的线程修改过了怎么办呢,自我循环重新去拿取内存地址的旧值再次去进行比较修改值,一直到比较成功,也比较消耗性能
悲观锁:也可以称为独占锁,典型的同步机制,每次只能有一个线程进入临界区(synchronized同步代码区域),也可以理解总有刁民想害朕,朕怕你们这帮刁民害我,每天只让一个刁民伺候朕
乐观锁:也可以称没有锁无锁化编程,利用判断旧数据来进行数据的提交校验,是否存在被别人改过的情况发生,也可以理解朕后宫佳丽千千万,朕每天让千千万个佳丽来伺候朕,朕最终只会让一个妃子待寝,哈哈
独占锁:保证每次只能有一个线程可以拿到锁,不能有两个线程同时拿到锁
公平锁:也可以称等待顺序锁,比喻当前我有一个同步区域代码(临界区),此时我有3个线程访问同步区域的代码,执行顺序为线程A,线程B,线程C,此时线程A进入同步代码区域,后续等待的线程都是按照顺序去获取锁资源的,不会存在按照时间轮转片机制随机性质的获取一个(即乱序竞争抢夺锁的情况),公平锁是不会出现这种乱序竞争抢夺锁的情况,都是顺序执行的先来先得的概率,比如是公平锁的话那我下面执行的顺序就应该是 线程B,线程C
轻量锁:也可以称无需阻塞锁,比喻CAS他的话就是一个轻量级的锁,通过自旋的方式让修改失败的线程重新比较,以及不需要通过阻塞线程的方式来保证线程共享数据的安全,这就是一种典型的轻量锁机制
重量锁:也可以称阻塞锁,比喻synchronized同步,如果此时有1百个线程访问我临界区(synchronized代码区域),每次只能进入一个线程,进了一个此时有99个线程在等待中这时可想我的锁开销资源有多大,并且还会产生竞争锁的情况, 可想而知重量级别有多大
2.使用一些常用的JDK提供的原子操作类
常用的
更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong
更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
更新引用类型:AtomicReference,AtomicMarkableReference, AtomicStampedReference
不常用的利用java反射改变某个对象中的字段类型:AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater
注意:上面的这些原子类底层统一使用了CAS
本次就使用一些常用的几个原子存在类
实战:
注意事项:
1.数组类原子操作类,数组不会存在对象引用问题,内部进行了克隆
2.引用类型原子存在类也不会产生对象引用问题,因为每次更改都会修改对象的引用
更新基本类型类实战:
具体代码:
public class BasicMain {
//CAS原子布尔
static AtomicBoolean AtomicBoolean=new AtomicBoolean(true);
//CAS原子Integer
static AtomicInteger atomicInteger=new AtomicInteger(0);
//CAS原子Long
static AtomicLong atomicLong=new AtomicLong(0);
public static void main(String[] args) {
for (int i = 0; i < 200; ++i) {
new Thread(()->{
//先+1后获取
atomicInteger.incrementAndGet();
}).start();
}
//由于atomic获取结果没有阻塞这里模拟一个
//如果当前主线程里面的子线程组还有线程的话我就把当前主线程变为就绪,等待子线程执行完毕先
while (Thread.activeCount()>1){
Thread.yield();
}
System.out.println("atomicInteger合计累加值:"+atomicInteger.get());
for (int i = 0; i < 300; ++i) {
new Thread(()->{
atomicLong.incrementAndGet();
}).start();
}
//由于atomic获取结果没有阻塞这里模拟一个
//如果当前主线程里面的子线程组还有线程的话我就把当前主线程变为就绪,等待子线程执行完毕先
while (Thread.activeCount()>1){
Thread.yield();
}
System.out.println("atomicLong合计累加值:"+atomicLong.get());
}
}
运行结果:
更新数组类实战:
具体代码:
public class AtomicArrayMain {
static int[] array=new int[]{1,2,3,4,5};
//注意内部使用了数组克隆,所以不会存在引用源数组的问题
static AtomicIntegerArray atomicIntegerArray=new AtomicIntegerArray(array);
public static void main(String[] args) {
for (int i = 0; i < 200; ++i) {
atomicIntegerArray.set(0,i);
}
while (Thread.activeCount()>1){
Thread.yield();
}
System.out.println(atomicIntegerArray.get(0));
}
}
运行结果:
更新引用类型实战:
包含解决ABA问题使用AtomicStampedReference,AtomicMarkableReference,跟多个cas更新字段合并成一个对象进行
具体代码:
public class ReferenceMain {
/** 使用对象合并多个字段进行CAS比较更新 **/
static class mergeField{
static class user{
private String name;
private int age;
public user(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "user{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
static user user=new user("刺客五六七",17);
static AtomicReference<user> atomicReference=new AtomicReference<>(user);
public static void main(String[] args) {
System.out.println("atomicReference初始值:"+atomicReference.get().toString());
System.out.println("------------------------------------------------------");
user newUser=new user("猪猪侠",15);
atomicReference.set(new user("1",1));
System.out.println("是否修改成功:"+atomicReference.compareAndSet(user,newUser));
System.out.println("atomicReference修改过的值:"+atomicReference.get().toString());
}
}
/** 使用stamped版本更新解决ABA问题**/
static class stampedSolve{
static AtomicStampedReference<String> stringAtomicStampedReference=new AtomicStampedReference<>("张三",0);
public static void main(String[] args) {
//旧版本号
int oldVersion=stringAtomicStampedReference.getStamp();
System.out.println("第一次修改是否成功:"+stringAtomicStampedReference.compareAndSet("张三","王五",
oldVersion,stringAtomicStampedReference.getStamp()+1));
System.out.println("第一次内容输出:"+stringAtomicStampedReference.getReference());
System.out.println("第二次修改是否成功:"+stringAtomicStampedReference.compareAndSet("王五","李四",
oldVersion,stringAtomicStampedReference.getStamp()+1));
System.out.println("第二次内容输出:"+stringAtomicStampedReference.getReference());
}
}
/** 使用marked版本更新解决ABA问题**/
static class markedSolve{
static AtomicMarkableReference<String> atomicMarkableReference=new AtomicMarkableReference("张三",false);
public static void main(String[] args) {
//旧版本号
Boolean marked=atomicMarkableReference.isMarked();
System.out.println("第一次修改是否成功:"+atomicMarkableReference.compareAndSet("张三","王五",
marked,true));
System.out.println("第一次内容输出:"+atomicMarkableReference.getReference());
System.out.println("第二次修改是否成功:"+atomicMarkableReference.compareAndSet("王五","李四",
marked,false));
System.out.println("第二次内容输出:"+atomicMarkableReference.getReference());
}
}
}
使用对象合并多个字段进行CAS比较更新:运行结果:
使用stamped版本更新解决ABA问题:运行结果:
使用marked版本更新解决ABA问题:运行结果: