java的CAS机制

CAS是java中的一种乐观锁实现方式,它能在很多场景下保证线程安全的同时有着很好的性能。

先看一个例子

package cas;

public class CASdemo {
	public static int count=0;
	
	public static void main(String[] args) {
		for(int i=0;i<2;i++) {
			new Thread(new Runnable() {
				public  void run() {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}										
					for(int j=0;j<1000;j++) {
						count++;
					}
				}
			}).start();
		}
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {

			e.printStackTrace();
		}
		System.out.println(count);
	}
}

我们同时开启两个线程对共享变量进行自加1000的操作,那么很明显它存在线程安全问题结果不为2000。
在这里插入图片描述

我们常见的解决方案是加上Synchronized关键字来保证线程安全。

package cas;

public class CASdemo {
	public static int count=0;
	
	public static void main(String[] args) {
		for(int i=0;i<2;i++) {
			new Thread(new Runnable() {
				public  void run() {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}										
					for(int j=0;j<1000;j++) {
						synchronized(CASdemo.class){   //加锁
						count++;
						}
					}
				}
			}).start();
		}
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {

			e.printStackTrace();
		}
		System.out.println(count);
	}
}

在这里插入图片描述
这时可以看结果为2000,说明synchronized保证了线程安全。

但是synchronized实现线程安全有一个很大的弊端就是性能问题。 使用Synchronized关键字时没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式内核模式的转换,性能代价比较高。

这个时候CAS机制就能很好的起到效果,java提供了java.util.concurrent.atomic包下一系列的原子操作类,它们都是实现了CAS机制。能保证原子性操作

package cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CASdemo {
	public static AtomicInteger count=new AtomicInteger(0);    //使用原子操作类
	
	public static void main(String[] args) {
		for(int i=0;i<2;i++) {
			new Thread(new Runnable() {
				public  void run() {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}										
					for(int j=0;j<1000;j++) {						
						count.incrementAndGet();     //调用自增方法
					}
				}
			}).start();
		}
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(count);
	}
}

在这里插入图片描述
可以看到它也保证了线程安全

 

那么CAS机制到底是怎样的?

CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。当我们 更新一个变量的时候,只有当内存地址V对应的实际值与旧的预期值A相等时,才会将值替换为B

在这里插入图片描述
假如内存地址V中有一个值为1.

现在有一个线程要对它执行加一操作,那么对于这个线程的A=1,B=2。而此时在在它提交更新之前,另一个线程抢先将值加为了2.。那么此时在这里插入图片描述

这个时候第一个线程提交更新,开始比较A值,发现不相等,提交失败。之后这个线程会自旋重新得到A值,计算B值。那么就变成了
A=2,B=3
再一次进行比较,发现A值相等,提交成功

如果此时又有一个线程抢先改变了A值,那么它就会继续自旋!

综上所述,可以总结,synchronized本质上是悲观锁,总是认为存在线程安全问题。CAS为乐观锁,总是认为线程安全不严重,所以compare and swap。

在java中,使用了CAS机制的有J.U.C下的Atomic包下的原子操作类,lock系列类。在jdk1.6之后,synchronized为了改善性能有个从偏向锁,到轻量锁,到重量锁的过程。在变为重量锁之前也是使用CAS机制。

 
当然CAS机制也存在一些缺点:因为它需要不断的去尝试更新,所以在高并发情况下,会占用CPU大量资源。它只能保证一个变量的原子性。还有一个更严重的问题ABA问题。

 

ABA问题

假如内存地址V上有一个值A
在这里插入图片描述
现在有两个线程要修改它为B值,此时线程1,2都获取了它的值,并且线程1先完成了修改,线程2阻塞住了。此时又进来一个线程3,此时状态大概为
在这里插入图片描述
线程3执行成功,值修改为A了,此时线程2仍然阻塞。所以此时,线程2并不知道值已经从A-》B又从B-》A

这就是ABA问题,在实际场景中,会造成线程不安全!!

ABA问题的解决方法

加一个版本号,不止比较值A,还比较版本号,每提交一次更新操作版本号就加一。java中AtomicStampedReference就是利用 版本号解决了这个问题。

tip:具体如何底层如何实现的CAS可以看看源码,是使用了unsafe提供的原子性操作。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值