无锁并发就是并发情况下对共享资源不进行上锁,不上锁意味着有资源被线程获取后另一个线程竞争不会进入阻塞。无锁设计时就是不断whlie true循序直至获取到资源就退出。这种经典设计就是CAS。所以CAS也必须是搭配while(true)使用。volatile关键字是通过缓存一致性协议来保证变量的可见性和有序性。JUC原子类内的方法CAS实现的。下面具体分析下。
CAS
ReentrantLock接口中其实采用了CAS的思想,这个方法底层来源于AQS。再底层是基于unsafe类的native方法去实现无锁并发。CAS调用只需要传入旧值与新值,如果旧值不正确则不会修改成功。CAS如果只用CompareAndSet的话会有ABA问题。所谓ABA问题就是Thread1将A改为B,Thread2又将B改为A,Thread3进行CAS时会成功,但其实过程中也被其他线程改过。ABA问题不会对结果产生影响,只是过程不透明。解决ABA问题就是加入版本号,或者改动标识位。类似实现JUC下有AtomicStampedReference,AtomicMarkableReference类。它们的CompareAndSet是需要多传入版本号,或者改动标识位的。测试类
public class TestCas {
public static void main(String[] args) {
Sell sell1 = new Sell(10000);
for (int i =0;i<100;i++){
new Thread(()->{
sell1.sell(10);
}).start();
}
SleepUtils.sleep(3);
System.out.println(sell1.getBalance());
}
}
class Sell {
private AtomicInteger balance;
public Sell(int value){
this.balance = new AtomicInteger(value);
}
public int getBalance(){
return balance.get();
}
public void sell(int amount){
while (true){
int balance1 = getBalance();
int next = balance1 - amount;
if (balance.compareAndSet(balance1,next)) {
break;
}
}
}
}
Volatile
volatile底层原理JMM是通过内存屏障去实现,有读屏障和写屏障。读屏障保证在读取共享变量所有的值都是主存最新的值,写屏障保证写入变量的值对所有工作内存可见。简单地说,被volatile修饰的变量当被改变时,会强制其它线程的工作内存中储存该值的引用失效,强制读取主内存的值。以下方法测试,如果不加volatile就不会结束循环。
@Slf4j
public class TestVolilite {
// 易变
volatile static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(true){
if(!run) {
break;
}
}
});
t.start();
SleepUtils.sleep(1);
run = false; // 线程t不会如预想的停下来
}
}
JUC原子类
JUC下面的类都是原子性的。但他们的底层实现都一致,比如AtomicInteger中,需要原子的修改一个整型值,底层都是unasfe的cas方法,但是该整型值是会被volatile修饰以保证其它线程可见性。
除整型外,还有bolean类型,long类型,原子数组,原子引用满足各种场景。具体方法可以看API。