CAS伪代码
boolean CAS(address, expectValue, swapValue) {
if (&address == expectedValue) {
&address = swapValue;
return true;
}
return false;
}
CAS的全称为 compare and swap,这是一个特殊的cpu指令,完成的是“比较和交换”
我们这里提到的expectValue,swapValue指的都是寄存器中的值
这里的if语句是来去比较address内存地址中的值是否和expected寄存器中的值是否相同,如果相同就会把swap寄存器的值和address中的值进行交换,并且返回true。
如果不相同,则不交换,返回false。
我们所说的这个CAS——CPU指令,他是具有原子性的,也就是说,他是线程安全的,我们可以在多线程来进行使用CAS来确保线程安全。
在CAS指令中,我们不用去进行加锁,也不会阻塞,也能够保证线程安全。
原子类介绍
处于java.util.concurrent.atomic包里
import java.util.concurrent.atomic.AtomicInteger;
public class Demo1 {
private static AtomicInteger count=new AtomicInteger(0);
//相当于原先使用
//private static int count=0;
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
for (int i = 0; i <50000 ; i++) {
// count++;
count.getAndIncrement();
//++count
// count.incrementAndGet();
//count+=n;
//count.getAndAdd(n);
}
});
Thread t2=new Thread(()->{
for(int i=0;i<50000;i++){
count.getAndIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count="+count);
}
}
运行结果
此处我们可以看到,没有加锁并没有出现错误.这里我们使用了CAS来确保线程安全.
伪代码实现
class AtomicInteger {
private int value;
public int getAndIncrement() {
int oldValue = value;
while ( CAS(value, oldValue, oldValue+1) != true) {
oldValue = value;
}
return oldValue;
}
这里我们的CAS就是只有value值和oldvalue寄存器的值一样的时候,才能进行交换,返回true,如果不相等,则不会进行交换,并且发回false,给oldValue寄存器重新赋值,再次进行CAS操作,直到CAS操作成功.
谈CAS实现自旋锁
public class SpinLock {
private Thread owner = null;
public void lock(){
// 通过 CAS 看当前锁是否被某个线程持有.
// 如果这个锁已经被别的线程持有, 那么就?旋等待.
// 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.
while(!CAS(this.owner, null, Thread.currentThread())){
}
}
public void unlock (){
this.owner = null;
}
}
当这个锁没有被线程持有的时候,即不等于null,则被当前的线程持有,如果已经持有线程了,则不会被当前线程持有,会一直进行while循环,通过这样的忙碌来等待效果.
此处自旋式的等并没有放弃cpu,因此不会再调度上有开销,但是会浪费更多的cpu资源.
相较于阻塞式的等,完全放弃了cpu,从cpu上离开,不会浪费cpu资源.
CAS的ABA问题,CAS确保线程安全的底层原理在于比较他的值是否有变化,如果进行了变化,则重新对寄存器的值进行赋值,如果没有则完成,但是在这里,如果有一个线程穿插执行,记为线程三,线程一将0改到50,线程2将50改到0,线程三再拿到之前第一个线程的0,再次进行操作,这样看貌似没有什么错误,但实则线程三拿到的0并非是正确的0,
假如在这里有个银行,A余额有1000元,进行取款500,但是点了一次没反应,再点了一次,
为了避免这样的情况
我们有两种解决方案:
1)我们对数据进行约束,规定数据的变化只能是单向的(只能增加或者减少),不能是双向的,(既增加又减少).
2)对于本身就必须双向变化的数据,我们引入一个版本号,这个版本号数字只能增加不能减少.