Java中CAS及常用类详解

本文介绍了CAS(CompareandSwap)原理,包括其操作步骤、内存位置的比较与更新过程,以及CAS在数据安全、线程同步和自旋锁中的运用。同时讨论了CAS的优缺点和ABA问题的解决方案,如使用AtomicStampedReference。
摘要由CSDN通过智能技术生成

CAS原理介绍

什么是CAS?

CAS是compare and swap,比较并交换,是实现并发算法时常用到的一种技术。它包含三个操作数:1.内存位置 2.期望原值 3.更新值。在执行CAS操作的时候,会将内存位置的值和期望原值相比较,如果二者相等,则处理器会将内存位置的值更新为更新值,否则处理器不做任何操作或者重试。多个线程对同一个值执行CAS操作,同一时间只会有一个线程成功。

CAS原理示意图

Created with Raphaël 2.3.0 CAS请求参数:内存位置的值(V) 、旧的期望值(A)、新值(B) 判断旧的期望值(A)是 否等于内存位置的值(V)? 用B的值更新V的值 不更新 yes no

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();
    }
}

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值