如果我问你在Java语言环境下何时使用CAS机制,你可能会说:出现线程不安全可能性的时候就是我们应当使用CAS机制的时候。但是这个说话虽然是正确的,但是太笼统以至于说了好像没说一样。如果你学过synchronized
关键字,你一定知道同步机制带来的内存上的损耗是很大的,比如频繁的上下文切换就是我们在使用synchronized
关键字时急需避免的。但是如果你了解CAS机制的话,你就会知道此机制有可能会导致线程占据CPU资源,如果在线程安全的条件下仍然使用CAS机制,那么就会带来不必要的CPU资源损耗。
首先给出使用CAS机制的原则:
解释:
for(;;)
)机制相结合使用,所以在自旋机制下,线程竞争越激烈,越多的线程在循环中等待资源释放,而这个过程是占据CPU资源的synchronized
关键字性能比CAS机制差final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();//得到访问锁对象的当前线程对象
int c = getState();//得到当前锁对象的状态
if (c == 0) { //状态为0,意味着没有任何线程占据着当前锁对象
if (compareAndSetState(0, acquires)) {//使用CAS机制将当前锁状态更新,只有一个线程会成功,返回true
setExclusiveOwnerThread(current);//将当前线程置为锁的独占线程
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//如果当前线程卡位占据锁对象的线程
int nextc = c + acquires;//得到当前线程重入锁后的状态
if (nextc < 0) // overflow//这是锁状态的非法值,如若此值,则抛出异常
throw new Error("Maximum lock count exceeded");
setState(nextc);//调用set方法,更新状态值。
return true;
}
return false;
}
以上代码是java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
中所定义的当前线程尝试获取资源的方法,可能你还没有学过AQS
机制,Lock
接口,但是通过我上述对代码的注释,相信你应该对这个代码块可以有一个大致的认识。
不知道你有没有注意到一点,上述代码有两处用不同的方法进行锁状态的更新:
if (compareAndSetState(0, acquires))
以及setState(nextc);
但是为何目的都是锁对象状态更新,实现方式却是一个CAS机制,一个普通的set
方法。
原因是上述原则中的第三点:CAS机制使用处可能出现线程不安全情况,而后者却是一定处于线程安全情况。下面来说说具体的判断原因:
if逻辑判断语句
中,只有成功的线程才会被设置为当前锁对象的独占线程;从CAS机制使用原则上我们还是可以看出一点,如果能笃定地根据代码逻辑判断出当前代码块是被单线程访问或者执行的,那么我们应当坚决拥护最简单的单线程中的写方法。不是说在学习好多线程知识之后我们在何时何处都应当使用多线程的写方法来保护线程安全性。如果多线程带来线程安全性保障是不必要的,那么多线程导致的额外损耗就是多余。