简单探索CAS算法原理
看了标题后,许多朋友会不禁发出疑问,什么是CAS算法? 简单来说,CAS为Compare and Swap的意思,即比较并交换的算法。
1.CAS算法理解
- JDK1.5增加了并法包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronize同步锁的一种乐观锁。JDK1.5之前Java语言是靠synchronized关键字来保证同步的,这是一种独占锁(就是有你没我的意思),也是悲观锁。
如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程),数据库记录始终处于加锁状态,可以想见,如果面对几百上千个并发,这样的情况将导致怎样的后果。乐观锁机制在一定程度上解决了这个问题。
对于CAS的理解,可以简单将CAS理解为一种无锁的算法,CAS中有3个操作数,内存值V,预期值A,以及要修改的新值B。当且仅当预期值A与内存值V相同时,将内存值 V修改为B,否则什么都不做。
2.以实例说明CAS算法的作用
前面我们了解到了i++的原子性问题:i++算法在代码层面只有一步操作,但交给CPU指令进行操作实际上分为3个步骤,即“读–改--写”。而前面我们知道volatile可以保证可见性,但是不能保证原子性。
- 实际上除了synchronize实现原子性外,CAS算法也可以保证数据变量的原子性。
在java.util.concurrent.atmoic包下提供了一些原子变量。所谓原子变量,就是类的小工具包,支持在单个变量上解除锁的线程安全编程。事实上,此包中的类可将voilatile值,字段和数组元素概念扩展到那些也提供原子条件更新操作的类。
在原子类变量中,如java.util.concurrent.atomic中的AtomicXXX,都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的CAS操作,而在java.util.concurrent中的大多数类在实现时都直接或间接的使用了这些原子变量类。
- 与前面的卖票程序一致的代码,这里采用多线程操作多条共享语句(i++)。代码如下,所以会发生多线程安全问题,这里会出现相同的数字。
public class MyRunnable implements Runnable {
//提供共享变量
static int i=0;
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//i++不是原子性操作,所以会发生多线程安全问题
System.out.println(i++);
}
}
}
public class MyTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
//利用for循环开启10个线程操纵共享资源,与i++语句
for (int i = 0; i < 10; i++) {
new Thread(myRunnable).start();
}
}
}
执行后结果会出现相同的数字:
- 这里除过利用sychronized解决原子性问题,还可以使用CAS算法来解决。代码如下:
//在java.util.concurrent.atmoic包下提供了一些原子变量。
// 所谓原子变量,就是类的小工具包,支持在单个变量上解除锁的线程安全编程。
import java.util.concurrent.atomic.AtomicInteger;
public class MyRunnable implements Runnable {
AtomicInteger i= new AtomicInteger(1);
//提供成员变量的get方法
public AtomicInteger getI() {
return i;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i.getAndIncrement());
}
}
}
public class MyTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
for (int i = 0; i < 10; i++) {
new Thread(myRunnable).start();
}
}
}
- 输出的结果没有相等的数字,即解决多线程安全问题,如下图。
- AtomicInteger.GetAndincrement 的实现用了乐观锁技术,调用了类sun.misc.Unsafe库里面的 CAS算法,用CPU指令来实现无锁自增。所以 AtomicInteger.GetAndincrement 的自增比用synchronized的锁效率倍增。
总结
关于CAS算法的介绍,到这里就结束了。对于CAS算法的理解这部分大家要对其处理的思想要弄清楚,面试中谈及多线程安全问题时也能与面试官聊聊CAS算法的理解,会很加分呦。