CAS原理介绍
什么是CAS?
CAS是compare and swap,比较并交换,是实现并发算法时常用到的一种技术。它包含三个操作数:1.内存位置 2.期望原值 3.更新值。在执行CAS操作的时候,会将内存位置的值和期望原值相比较,如果二者相等,则处理器会将内存位置的值更新为更新值,否则处理器不做任何操作或者重试。多个线程对同一个值执行CAS操作,同一时间只会有一个线程成功。
CAS原理示意图
CAS有3个操作数,内存位置的值(V),期望原值(A),更新值(B).假设某次CAS操作要更新某个内存位置的值,首先此次CAS操作会先获取内存位置此时的值,假设为2,那么 期望原值(A) 就是2,然后CAS操作对2这个值进行+3操作,此时就得到了更新值(B) 等于5,线程计算完毕,准备用最后的计算结果更新值(B) 更新对应的内存位置的值(V) 。但是在更新之前,需要检查一下,在线程计算期间,这个内存位置的值(V) 有没有被其他线程修改更新过,如果被别的线程更新过,那么本线程就不能直接把本次计算结果更新到内存位置的值(V) 了,因为这会有线程安全问题,如果没有被别的线程更新过,那么才可以把本次计算结果更新到内存位置的值(V) 。那么怎么判断内存位置的值(V) 在本线程计算期间有没有被其他线程更新过呢?那就是通过比较内存位置的值(V) 和期望原值(A) 是否相等来确定,如果相等,就说明没有被其他线程更新过(当然还会有ABA问题,这个后面说,暂时不考虑),如果不相等,说明被其他线程更新过。遇到内存位置的值(V) 和期望原值(A) 不相等的情况下,本线程有两种策略,一是什么都不做,二是重来,当线程选择重来时,这就是自旋。
CAS代码demo
package multiThreads;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(6, 10) + "," + atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5, 10) + "," + atomicInteger.get());
}
}
运行结果:
CAS如何保证的数据安全?
方法解析
public final boolean compareAndSet(int expect, int update) {
//this 当前原子类对象
//valueOffset 要操作对象中属性地址的偏移量
//expect 期望原值
//update 更新值
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
CAS的原理?如果知道,谈谈你对Unsafe类的理解。
i++是线程不安全的,那为什么AtomicInteger.getAndIncrement()是线程安全的?
原子引用
除了Java自己定义的这几个原子类型,我们是否可以自定义原子类型?
可以,类似AtomicBook,AtomicOrder这种。
代码如下:
package multiThreads;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
public static void main(String[] args) {
AtomicReference<User> userAtomicReference = new AtomicReference<>();
User zs = new User("zs", 22);
User ls = new User("ls", 27);
userAtomicReference.set(zs);
System.out.println(userAtomicReference.compareAndSet(zs, ls) + "," + userAtomicReference.get());
System.out.println(userAtomicReference.compareAndSet(zs, ls) + "," + userAtomicReference.get());
}
}
class User {
String name;
int aga;
public User() {
}
public User(String name, int aga) {
this.name = name;
this.aga = aga;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAga() {
return aga;
}
public void setAga(int aga) {
this.aga = aga;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return aga == user.aga && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name, aga);
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", aga=" + aga +
'}';
}
}
运行结果:
CAS与自旋锁,借鉴CAS思想
什么是自旋锁?
自行实现自旋锁
代码如下:
package multiThreads;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLockDemo {
private AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock() {
System.out.println(Thread.currentThread().getName() + " try to lock");
Thread thread = Thread.currentThread();
//获取锁失败就一直尝试获取锁,直到成功为止
while (!atomicReference.compareAndSet(null, thread)) {
}
System.out.println(Thread.currentThread().getName() + " locked");
}
public void unlock() {
System.out.println(Thread.currentThread().getName() + " try to unlock");
Thread thread = Thread.currentThread();
if (atomicReference.compareAndSet(thread, null)) {
System.out.println(Thread.currentThread().getName() + " unlocked");
}
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.lock();
try {
TimeUnit.MILLISECONDS.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.unlock();
}, "a").start();
new Thread(() -> {
spinLockDemo.lock();
try {
TimeUnit.MILLISECONDS.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.unlock();
}, "b").start();
}
}
运行结果如下:
CAS的缺点
1.CAS是乐观锁的一种实现,如果竞争非常激烈的情况下,会导致线程CAS操作失败的概率剧增,线程一直循环等待,造成cpu空转,浪费cpu资源。
2.ABA问题
比如:现有一个用单向链表实现的堆栈,栈顶为A,这时线程T1已经知道A.next为B,然后希望用CAS将栈顶替换为B:head.compareAndSet(A,B);在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再pushD、C、A。而对象B此时处于游离状态:此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B,但实际上B.next为null,其中堆栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,平白无故就把C、D丢掉了。
解决方案:使用AtomicStampedReference
代码如下:
package multiThreads;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceTest {
public static void main(String[] args) {
Book javaBook = new Book("java", 1);
Book mysqlBook = new Book("MYSQL", 1);
AtomicStampedReference<Book> reference = new AtomicStampedReference<>(javaBook, 1);
Book reference1 = reference.getReference();
int stamp = reference.getStamp();
System.out.println("reference1:" +reference1 + ",stamp:" + stamp);
System.out.println(reference.compareAndSet(javaBook, mysqlBook, stamp, stamp+1));
System.out.println("reference2:" + reference.getReference() + ", stamp2:" + reference.getStamp());
}
}
class Book {
private String name;
private int id;
public Book() {
}
public Book(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
}
运行结果:
多线程示例:
package multiThreads;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class Test918 {
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
new Thread(() -> {
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + " 获取的stamp是:" + atomicStampedReference.getStamp());
try {
TimeUnit.MILLISECONDS.sleep(50L);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(thread.getName() + "是否更改成功:" + b + ", 此时值为:" + atomicStampedReference.getReference() + ",stamp为:" + atomicStampedReference.getStamp());
try {
TimeUnit.MILLISECONDS.sleep(50L);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean c = atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(thread.getName() + "是否更改成功:" + c + ", 此时值为:" + atomicStampedReference.getReference() + ",stamp为:" + atomicStampedReference.getStamp());
}, "a").start();
new Thread(() ->{
Thread thread = Thread.currentThread();
int stamp = atomicStampedReference.getStamp();
Integer reference = atomicStampedReference.getReference();
System.out.println(thread.getName() + " 获取的stamp是:" + stamp + ", 值是:" + reference);
try {
TimeUnit.MILLISECONDS.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean d = atomicStampedReference.compareAndSet(reference, 101, stamp, stamp + 1);
System.out.println(thread.getName() + "是否更改成功:" + d + ", 此时值为:" + atomicStampedReference.getReference() + ",stamp为:" + atomicStampedReference.getStamp());
}, "b").start();
}
}
运行结果: