一、前言
了解CAS,首先要清楚JUC,那么什么是JUC呢?JUC就是java.util.concurrent包的简称。它有核心就是CAS与AQS。CAS是java.util.concurrent.atomic包的基础,如AtomicInteger、AtomicBoolean、AtomicLong等等类都是基于CAS。
什么是CAS呢?全称Compare And Swap,比较并交换。CAS有三个操作数,内存值V,旧的预期值E,要修改的新值N。当且仅当预期值E和内存值V相同时,将内存值V修改为N,否则什么都不做。
二、实例
如果我们需要对一个数进行加法操作,应该怎样去实现呢?我们模拟多个线程情况下进行操作。
ThreadDemo.java 实现一个Runnable接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package com.spring.security.test; public class ThreadDemo implements Runnable { private int count = 0 ; @Override public void run() { for ( int i = 0 ; i < 100 ; i++) { addCount(); } } private void addCount() { count++; } public int getCount() { return count; } } |
ThreadTest.java 创建线程池,提交10个线程执行,预期结果应该是1000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.spring.security.test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadTest { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool( 10 ); ThreadDemo threadDemo = new ThreadDemo(); for ( int i = 0 ; i < 10 ; i++) { threadPool.submit(threadDemo); } threadPool.shutdown(); System.out.println(threadDemo.getCount()); } } |
运行结果:874 或其他,与预期结果不符合。
执行出来的结果并不是想象中的结果。这是为什么呢?这跟线程的执行过程有关。
所以我们需要在改变count,将值从高速缓冲区刷新到主内存后,让其他线程重新读取主内存中的值到自己的工作内存。
此时可以用volatile关键字。它的作用是保证对象在内存中的可见性。
修改ThreadDemo中的count字段
private volatile int count = 0;
此时执行结果:900 或其他,与预期结果不符合。
此时还是并未得出正确执行结果。为什么?听我细细道来。
线程安全主要体现在三个方面:
- 原子性:提供了互斥访问,同一时刻只能有一个线程对它进行操作
- 可见性:一个线程对主内存的修改可以及时的被其他线程观察到
- 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序
目前可见性已经实现了,缺少原子性的操作,因为同一时刻,多个线程对其操作,会将改动后的最新值读取到自己的工作内存进行操作,最终只能得到后一个执行线程操作的结果,所以相当于少了一步操作,就会造成数据的不一致。
此时可以使用JUC的Atomic包下面的类来进行操作。
Atomic类是使用CAS+volatile来实现原子性与可见性的。
我们来改造一下TheadDemo.java中的实现方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package com.spring.security.test; import java.util.concurrent.atomic.AtomicInteger; public class ThreadDemo implements Runnable { private AtomicInteger count = new AtomicInteger( 0 ); @Override public void run() { for ( int i = 0 ; i < 100 ; i++) { // 递增 count.getAndIncrement(); } } public int getCount() { return count.get(); } } |
执行结果: 1000,符合预期值。
接下来我们来分析一下AtomicInteger类的源码:
1
2
3
4
5
6
7
8
9
10
11
|
private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger. class .getDeclaredField( "value" )); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; |
Unsafe类是不安全的类,它提供了一些底层的方法,我们是不能使用这个类的。AtomicInteger的值保存在value中,而valueOffset是value在内存中的偏移量,利用静态代码块使其类一加载的时候就赋值。value值使用volatile,保证其可见性。
1
2
3
4
5
6
7
8
|
/** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt( this , valueOffset, 1 ); } |
1
2
3
4
5
6
7
8
|
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this .getIntVolatile(var1, var2); } while (! this .compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; } |
var1表示当前对象,var2表示value在内存中的偏移量,var4为增加的值。var5为调用底层方法获取value的值
compareAndSwapInt方法通过var1和var2获取当前内存中的value值,并与var5进行比对,如果一致,就将var5+var4的值赋给value,并返回true,否则返回false
由do while语句可知,如果这次没有设置进去值,就重复执行此过程。这一过程称为自旋。
compareAndSwapInt是JNI(Java Native Interface)提供的方法,可以是其他语言写的。
三、与synchronized比较
使用synchronized进行加法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package com.spring.security.test; public class ThreadDemo implements Runnable { private int count = 0 ; @Override public void run() { for ( int i = 0 ; i < 100 ; i++) { // 递增 synchronized (ThreadDemo. class ) { count++; } } } public int getCount() { return count; } } |
运行结果: 1000,符合预期值。
444
使用synchronized和AtomicInteger都能得到预期结果,但是他们之间各有什么劣势呢?
synchronized是重量级锁,是悲观锁,就是无论你线程之间发不发生竞争关系,它都认为会发生竞争,从而每次执行都会加锁。
在并发量大的情况下,如果锁的时间较长,那将会严重影响系统性能。
CAS操作中我们可以看到getAndAddInt方法的自旋操作,如果长时间自旋,那么肯定会对系统造成压力。而且如果value值从A->B->A,那么CAS就会认为这个值没有被操作过,这个称为CAS操作的"ABA"问题。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://www.cnblogs.com/yl-space/p/13390516.html