Java 并发锁 CAS基本使用和源码分析
1:介绍
CAS也是并发编程常用的类之一,它是基于原子性的操作来进行锁机制的。
实现原子操作是可以使用锁来执行的,当我们使用synchronized关键字的时候,是基于阻塞机制的锁,当一个线程拥有了锁,其他线程在调用的时候就必须等待。
当线程出现高并发,资源竞争激烈,还有可能死锁等情况,使用synchronized这种粗粒度的锁就有点不太好使用了。这个时候就需要原子操作了(本人喜欢叫乐观锁)。
当前的电脑处理器都支持CAS()指令,每一个CAS操作过程都包含三个运算符:一个内存地址,一个期望的值,一个新值。当期望的值和内存地址存放的值一样的时候,
就将新值赋值到内存地址上面的值,如果不一样就不做任何的事情,返回内存地址的里面的值。
CAS实现锁的机制:在一个循环里面一直在循环,直到新值赋值成功。一下就是简单的流程图:

2:简单的代码实现
public class AtomicTest { static Integer count =100; static AtomicInteger atomicInteger = new AtomicInteger(0); static CountDownLatch latch = new CountDownLatch(count); private static class Worker implements Runnable{ @Override public void run() { atomicInteger.getAndIncrement(); latch.countDown(); } } public static void main(String[] args) throws Exception { for (int i=0;i<count;i++){ Thread thread = new Thread(new Worker()); thread.start(); } latch.await(); System.out.println("计算之后的结果:"+atomicInteger.get()); } }
结果

以上就是实现了一个简单的累加计数器。
3:源码解析
当new AtomicInteger(0),就是将0复制到value上面。value是一个volatile修饰的,也就是当线程的对数据修改的时候,其他线程也会收到修改完之后的数据。
会初始化一个unsafe对象,还有一个静态代码,它里面就是做给内存地址赋值初始值的,返回valueOffset偏移量(方便在内存查找吧)。
由于objectFieldOffset是一个native方法,没法看到具体的代码(这里面只是个人的理解)。



当调用getAndIncrement方法的时候,又会继续调用unsafe.getAndAddInt方法,this当前对象,valueOffset是一个偏移量,1是我们要进行新增的值。
getAndAddInt方法,该方法是一个dowhile语句,也就是如果不满足条件就会一直循环。getIntVolatile方法就是将对象和偏移量来查找放在内存当中罪行的值,
然后将最新的值进行+1操作,compareAndSwapInt方法就是执行前面所说的替换操作,如果替换成功返回TRUE,替换失败返回FALSE。
返回拿到的内存地址存放的数据,不是新增之后的数据。




由于好多方法都是native修饰了,导致了源码不能看,所以就按本人理解的意思就行讲解。
3:CAS带来的问题
ABA问题
因为在执行CAS的时候,是进行值的比较,如果这个值之前进行+5,然后又进行-5,最终变成0,然后进行比较的还是0所以就行了替换了。
很显然这个值是变化的了。解决的办法就是加一个版本号,1A->2B->3C,这样子就可以防止值进行修改就不知道了。
性能问题
因为CAS是采用自旋的方式进行锁的操作,所以会对性能有一定的影响。
只能保证一个变量的共享
如果我们要有多个变量的话,怎么办?采用对象的方式进行替换,JDK提供了AtomicReference类来保证对象的原子性。
以上就是简单的CAS操作,纯属个人了理解,有不对的地方往告知,谢谢!!!
当然atomic包下面还有很多个类,我会尽可能都解析出来的。

浙公网安备 33010602011771号