CAS:简单的来说就是比较交换!那么比较的是什么?交换的又是什么呢? CAS有三个操作数,V,A,B。要比较的就是V和A,当V和A相等的时候,就将V的值更新为B. 感觉就像“天王盖地虎”对“小鸡炖蘑菇”一样,暗号对上了(V==A)就可以进行下一步的操作(更新)了 上面这段描述可以简单的伪代码表示为:
int value;
public synchronized int compareAndSwap(int A,int B){
int V = value;
if(V==A){
value=B;
}
return V;
}
当且仅当V和A匹配成功时,才会将value替换成新的值。也就是说我认为V的值应该是A,如果是,那么将V的值更新为B。注意无论如何,我们返回的都是V原有的值。 所以compareAndSet
可以这么写:
//如果是true的话,说明值更新成功,否则则是更新失败。
public synchronized int compareAndSet(int A,int B){
return (A==compareAndSwap(A,B);
}
简单举个例子:
1、首先定义一个类,名为CAS,该类实现了Runnable,为CAS类提供V、A、B三个变量
private AtomicBoolean V = new AtomicBoolean(true);
private boolean A = true;
private boolean B = false;
2、为CAS类设计两个方法在lock方法模拟自旋,unlock方法将V值复位:
//模拟自旋
private void lock() {
while (!V.compareAndSet(A, B)) {
// do nothing
println(Thread.currentThread().getName() + "--lock--进入自旋转-");
// 加一个sleep操作,防止while循环过快执行
sleep(1000);
}
}
//V值复位为false
private void unlock() {
V.compareAndSet(B, A);
println(Thread.currentThread().getName() + "----unlock-V-" + V.get());
}
public void sleep(long time) {
try {
Thread.sleep(time);
} catch (Exception e) {
}
}
看lock方法其实很简单,就是一个while循环不断比较!V.compareAndSet(A, B),为了防止调用速度过快,博主可以sleep了一秒钟。 3、在Runnable的run方法中这么调用:
public void run() {
lock();
println(Thread.currentThread().getName() + " 开始工作,此时V的值修改为==" + V.get());
sleep(5000);
unlock();
}
4、开启两个Thread执行CAS:
public static void main(String args[]) {
CAS cas = new CAS();
Thread a = new Thread(cas);
a.setName("线程A");
Thread b = new Thread(cas);
b.setName("线程B");
a.start();
b.start();
}
5、执行结果就是:
线程A 开始工作,此时V的值修改为==false
线程B--lock--进入自旋转-
线程B--lock--进入自旋转-
线程B--lock--进入自旋转-
线程B--lock--进入自旋转-
线程B--lock--进入自旋转-
线程B--lock--进入自旋转-
线程A----unlock-V-true
线程B 开始工作,此时V的值修改为==false
线程B----unlock-V-true
从上面的打印可以看出CAS对于两个线程来说,都没有挂起;都在各自执行自己的东西,比如A线程执行自己的打印,B线程就不断的执行cas自旋转。所以CAS的也成为无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步,所以可以用CAS来模拟实现乐观锁,为了区别,这里可以简单的写一个demo,使用sun.misc.Lock来改写我们的run方法:
//定义一把锁
// 定义一把锁
private Lock lock = new Lock();
public void run() {
// 加锁
try {
lock.lock();
PrintUtils.println(Thread.currentThread().getName() + "-获取到了锁--");
} catch (InterruptedException e) {
}
PrintUtils.println(Thread.currentThread().getName() + " 开始工作五秒钟。。。。。");
ThreadUtils.sleep(5000);
PrintUtils.println(Thread.currentThread().getName() + "工作完毕,释放锁-");
lock.unlock();
}
继续执行上面的main方法,运行效果如下(可以看出A获取锁的时候,B就阻塞住了):
线程A-获取到了锁--
线程A 开始工作五秒钟。。。。。
线程A工作完毕,释放锁-
线程B-获取到了锁--
线程B 开始工作五秒钟。。。。。
线程B工作完毕,释放锁-
对于上面的CAS的代码,如果不用lock和unlock的话,我们也可以把run方法改写成如下所示:
public void run() {
if (V.compareAndSet(A, B)) {// 此时当前线程符合要求
println(Thread.currentThread().getName() + " 开始工作,此时V修改为==" + V.get());
sleep(5000);
V.set(A);
println(Thread.currentThread().getName() + "unlock-V-"+V.get());
} else {
println(Thread.currentThread().getName() + "--lock--进入自旋转-");
// 防止StackOverflowError
sleep(1000);
//invoke self,模拟CAS
run();
}
}
需要注意到是else分支调用run方法自己的时候,之所以sleep(1000),是为了防止调用过快,造成StackOverflowError的错误!
当然还有一个更优雅的自旋实现方法,用AtomicReference这个类!代码如下:
private AtomicReference owner = new AtomicReference();
@Override
public void run() {
lock();
PrintUtils.println(Thread.currentThread().getName() + " 开始工作五秒钟....");
ThreadUtils.sleep(5000);
unlock();
}
//模拟自旋
private void lock() {
Thread current = Thread.currentThread();
while(!owner.compareAndSet(null, current)) {
PrintUtils.println(current + " 开始自旋" );
ThreadUtils.sleep(1000);
}
}
private void unlock() {
Thread current = Thread.currentThread();
PrintUtils.println(current.getName() + "工作完毕");
owner.compareAndSet(current, null);
}
运行效果如下:
线程A 开始工作五秒钟....
Thread[线程B,5,main] 开始自旋
Thread[线程B,5,main] 开始自旋
Thread[线程B,5,main] 开始自旋
Thread[线程B,5,main] 开始自旋
Thread[线程B,5,main] 开始自旋
Thread[线程B,5,main] 开始自旋
线程A 工作完毕
线程B 开始工作五秒钟....
线程B 工作完毕
自旋转锁保证了在不阻塞的情况下对共享资源的互斥访问,自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。但是其缺乏公平性。为什么这么说呢?考虑一个现实的场景,博主高中的时候去学校食堂吃饭,在一个小窗口打饭的时候,刚开始几乎没人排队,然后大家一拥而上,跟打仗似的;但是就一个打菜的阿姨(共享资源),当阿姨跟一个人打菜的时候,后面的就得等着;但是当前一个人打完之后,后面的谁劲大,谁挤到前面就有优先打到菜的好处,根本没有什么先来后到这一说。这就不公平了。同样的当一个自旋锁被使用期间,别的需要该锁CPU就需要等待,一旦锁被释放,这些等待的CPU谁会首先获取锁呢?那就不得而知了,有可能最先请求锁的那个CPU最后获得锁。
那么怎么解决这个问题呢?排队自旋锁应运而生,就像后来博主的高中食堂开始排队打菜一样,具体什么是排队自旋锁,后面会继续说明 关于CAS,就简单的介绍到这儿,如有不当之处欢迎批评指正,共同学习和提高。博客demo代码传送门