CAS算法为了解决什么问题?
我们为了在多线程下保持数据同步,不得不在不同的线程间进行锁竞争时,这样总不可避免相互等待,从而阻塞当前线程。这样导致的性能低下……
什么是CAS算法
如果我们让每个线程拥有各自独立的变量副本,当各个线程在并行计算时,无需相互等待对方的结果,最后将大家运行的结果拿出来对比,对比“胜出”的就为最终的结果,所以这是一种基于比较交换的无锁并发控制方法。
算法优点
虽然无锁算法的设计和实现更为复杂的多,但是它有以下优点:
- 由于其非阻塞性,它对死锁问题天生免疫,既无死锁问题
- 线程间的相互影响也远远比基于锁的方式要小
- 使用无锁的方式没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,因此它要比有锁的方式拥有更优越的性能
CAS算法对比的思路
CAS算法拥有3个参数,它们分别是:
- V:表示要更新的变量
- E:表示预期值
- N:表示新值
对比方案
- 仅当更新的变量V和预期值E相等的时候,将新值N赋值给变量V,最后返回V
用代码的方式去理解:
if(V == E) V = N;
return V
- 当多个线程同时使用CAS操作一个变量时,只有一个会胜出(能够更新V的),其余失败的可以再次尝试或者放弃
- 备注:大部分现代的处理器已经支持原子化的CAS指令
代码
也许上面的原理讲解都很枯燥难懂,但是使用方式却特别简单,先看下面代码,再去理解原理可能会更好
我现在希望用3个线程去,将一个数acount,从0加到1百万,可以用下面代码实现
- 我先用线程池开启3个线程,然后去调用**AtomicThread **类
AtomicInteger acount = new AtomicInteger(0); //这行代码,应该放在成员变量中
//下面代码放在main方法中
ExecutorService exe = Executors.newFixedThreadPool(3);//线程池
AtomicThread atomic = new AtomicThread();
for(int i = 0;i < 3;i++){
exe.submit(atomic);
}
- 下面重点在AtomicThread类中,关键代码就一行
acount.incrementAndGet()
public class AtomicThread implements Runnable{
@Override
public void run() {
int v = acount.incrementAndGet();
while (v < 1000000){ //实际可能会加到1000002,不过不重要
v = acount.incrementAndGet(); // 关键代码
}
System.out.println("V=" + v);
}
}
原理说的那么复杂,其实调用也就这么一行代码,很简单吧!下面,我们再重点看看AtomicInteger类相关的API:
- int get() : 取得当前值
- set(int newValue) : 设置当前值
- int getAndSet(int newValue) : 设置新值,并返回旧值
- boolean compareAndSet(int expect, int u) : 这个就对应上面说的“对比”方案了
- int getAndIncrement() : 当前值加1,返回旧值 类似于 i++
- int getAndDecrement() : 当前值加1,返回旧值 类似于 i–
- int getAndAdd(int delta) : 当前值加delta,返回旧值
- int incrementAndGet() : 当前值加1,返回新值 类似 ++i
- int decrementAndGet() : 当前值减1,返回新值 类似于 --i
- int addAndGet(int delta) : 当前值加delta,返回新值
测试代码
下面是测试CAS算法和传统加锁的方式的效率高低,不用说CAS算法肯定胜出(我的版本是JDK 1.8),不然我就不用写这篇博客啦,哈哈
import org.junit.jupiter.api.Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import static javafx.scene.input.KeyCode.L;
public class TestAtomic {
private static final int MAX_THREADS = 3;
private static final int TASK_COUNT = 3;
private static final int TARGET_COUNT = 1000000;//循环100万次
private AtomicInteger acount = new AtomicInteger(0);
private int count = 10;
protected synchronized int inc(){ return ++count;}
protected synchronized int getCount(){return count;}
//有锁的添加方式
public class SyncThread implements Runnable{
protected String name;
protected long starttime;
TestAtomic out;
public SyncThread(TestAtomic out, long starttime){
this.out = out;
this.starttime = starttime;
}
@Override
public void run() {
int v = out.inc();
while (v < TARGET_COUNT){
v = out.inc();
}
long endtime = System.currentTimeMillis();
System.out.println("SyncThread speed:" + (endtime - starttime) + "ms" + "V=" + v);
}
}
//
public class AtomicThread implements Runnable{
protected long starttime;
public AtomicThread(long starttime){
this.starttime = starttime;
}
@Override
public void run() {
int v = acount.incrementAndGet();
while (v < TARGET_COUNT){
v = acount.incrementAndGet();
}
long endtime = System.currentTimeMillis();
System.out.println("AtomicThread speed:" + (endtime - starttime) + "ms " + "V=" + v);
}
}
@Test
public void testSync() throws InterruptedException{
ExecutorService exe = Executors.newFixedThreadPool(MAX_THREADS);
long starttime = System.currentTimeMillis();
SyncThread sync = new SyncThread(this,starttime);
for(int i = 0;i < TASK_COUNT;i++){
exe.submit(sync);
}
Thread.sleep(10000);
}
@Test
public void testAtomic() throws InterruptedException{
ExecutorService exe = Executors.newFixedThreadPool(MAX_THREADS);
long starttime = System.currentTimeMillis();
AtomicThread atomic = new AtomicThread(starttime);
for(int i = 0;i < TASK_COUNT;i++){
exe.submit(atomic);
}
Thread.sleep(10000);
}
//如果不会使用Junit测试,可以使用下面的main方法,效果一样,记得删除方法上面的@Test标签
// public static void main(String[] args) throws InterruptedException {
// TestAtomic testAtomic = new TestAtomic();
// testAtomic.testSync();
// testAtomic.testAtomic();
// }
}
- 如果不会使用Junit测试,可以使用最下面的main方法,效果一样,记得删除方法上面的@Test标签
我测试的效果差了5倍左右