专业的JAVA编程教程与资源

网站首页 > java教程 正文

并发编程之CAS(并发编程是啥意思)

temp10 2025-07-19 22:31:46 java教程 2 ℃ 0 评论

CAS概述

CAS(Compare And Swap,比较并交换)通常是之指一种原子操作,针对一个变量首先比较它的内存之与某个期望值是否相等,如果相等就给它赋值新值。CAS操作原子性是直接在硬件层面上得到保证的。CAS可以看作是一种乐观锁(对比数据库的乐观锁、悲观锁)的实现。Java原子类的递增操作就是通过CAS的自旋操作实现的。CAS是一种无锁的算法,在不使用锁(没有线程阻塞)的情况下实现多线程之间的变量同步。

CAS应用

在Java中CAS操作由Unsafe类提供支持,该类提供了三种不同类型变量的操作的native方法,由Java虚拟机提供具体实现,不同虚拟机的实现方式可能会略有差别。

并发编程之CAS(并发编程是啥意思)

定义一个UnsafeFactory工厂类,通过反射的方式获取Unsafe对象

import java.lang.reflect.Field;

/**
 * Unsafe工厂类
 * @Author warrior
 **/
public class UnsafeFactory {
    /**
     * 获取 Unsafe 对象
     *
     * @return
     */
    public static Unsafe getUnsafe() {
        try {
            Field filed = Unsafe.class.getDeclaredField("theUnsafe");
            filed.setAccessible(true);
            return (Unsafe) filed.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取字段内存偏移量
     *
     * @param unsafe
     * @param clazz
     * @param fieldName
     * @return
     */
    public static long getFieldOffset(Unsafe unsafe, Class clazz, String fieldName) {
        try {
            long fieldOffset = unsafe.objectFieldOffset(clazz.getDeclaredField(fieldName));
            return fieldOffset;
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }
}

调用Unsafe#compareAndSwapInt方法进行CAS操作

import sun.misc.Unsafe;

/**
 * CAS 应用案例
 * @Author warrior
 **/
public class CASDemo {

    public static void main(String[] args) {
        Entity entity = new Entity();

        // 获取 Unsafe 对象
        Unsafe unsafe = UnsafeFactory.getUnsafe();
        // 获取字段内存偏移量
        long fieldOffset = UnsafeFactory.getFieldOffset(unsafe, Entity.class, "x");
        boolean successful = false;
        /**
         * 模拟CAS操作
         * 4个参数分别是:对象实例、字段的内存偏移量、字段期望值、字段更新值
         */
        successful = unsafe.compareAndSwapInt(entity, fieldOffset, 0, 1);
        System.out.println(successful+ "\t" + entity.x);
        successful = unsafe.compareAndSwapInt(entity, fieldOffset, 1, 3);
        System.out.println(successful+ "\t" + entity.x);
        successful = unsafe.compareAndSwapInt(entity, fieldOffset, 1, 6);
        System.out.println(successful+ "\t" + entity.x);
    }

    static class Entity {
        int x;
    }

}

CAS ABA问题

CAS算法实现的一个重要前提是需要取出内存某个时刻的值,在下一个时刻进行比较和替换,那么在这个时间差下会导致数据的变化。当多个线程对一个原子类进行操作时,某个线程短时间内将原子类值从A更改成B,又马上将其修改成A,此时其它线程未感知还是会修改成功。

import java.util.concurrent.atomic.AtomicInteger;

/**
 * CAS ABA 案例
 *
 * @Author warrior
 **/
public class CASABADemo {

    //定义一个原子变量
    private static AtomicInteger atomicInteger = new AtomicInteger(1);

    public static void main(String[] args) {

        /**
         * 创建一个线程将原子变量从1修改到3
         */
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                int value = atomicInteger.get();
                System.out.println(Thread.currentThread().getName() + ",获取到的值:" + value);
                try {
                    //休眠1s
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                boolean success = atomicInteger.compareAndSet(value, 3);
                if (success) {
                    System.out.println(Thread.currentThread().getName() + " 修改成功,将值从" + value + "修改成3");
                }else {
                    System.out.println(Thread.currentThread().getName()+" 修改失败");
                }
            }
        },"thread_1");

        /**
         * 创建一个线程将原子变量从1修改成2在修改成1
         */
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int value = atomicInteger.get();
                System.out.println(Thread.currentThread().getName() + ",获取到的值:" + value);
                boolean success = atomicInteger.compareAndSet(value, 2);
                if (success) {
                    System.out.println(Thread.currentThread().getName() + " 修改成功,将值从" + value + "修改成2");
                }else {
                    System.out.println(Thread.currentThread().getName()+" 修改失败");
                }

                value = atomicInteger.get();
                success = atomicInteger.compareAndSet(value, 1);
                if (success) {
                    System.out.println(Thread.currentThread().getName() + " 修改成功,将值从" + value + "修改成1");
                }else {
                    System.out.println(Thread.currentThread().getName()+" 修改失败");
                }
            }
        },"thread_2");

        //开启线程
        thread1.start();
        thread2.start();
    }
}

ABA解决方案

数据库有个锁称为乐观锁,是一种基于数据版本实现数据同步的机制,每次修改一次数据,版本就会进行累加。同样,Java也提供了相应的原子引用类AtomicStampedReference。reference即我们实际存储的变量,stamp是版本,每次修改可以通过+1保证版本唯一性。这样就能保证每次修改后版本也会往上递增。

import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.LockSupport;

/**
 * AtomicStampedReference解决ABA问题案例
 *
 * @Author warrior
 **/
public class AtomicStampedReferenceDemo {

    //定义AtomicStampedReference原子变量    Pair.reference值为1, Pair.stamp为1
    private static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(1, 1);

    public static void main(String[] args) {
        /**
         * 创建线程1,将原子变量值从1修改到3
         */
        Thread thread1 = new Thread(() -> {
            int value = stampedReference.getReference();
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + ",获取值为:" + value + ",版本为:" + stamp);

            //阻塞1s
            LockSupport.parkNanos(1000L);

            //将值从1修改为3
            if (stampedReference.compareAndSet(value, 3, stamp, stamp + 1)) {
                System.out.println(Thread.currentThread().getName() + ",修改值成功,从1修改为3,版本为:" + stamp);
            } else {
                System.out.println(Thread.currentThread().getName() + ",修改值失败,版本为:" + stamp);
            }
        }, "thread1");

        /**
         * 创建线程2,将原子变量值从1修改到2再修改到1
         */
        Thread thread2 = new Thread(() -> {
            int value = stampedReference.getReference();
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + ",获取值为:" + value + ",版本为:" + stamp);
            //将值从1修改为2
            if (stampedReference.compareAndSet(value, 2, stamp, stamp + 1)) {
                System.out.println(Thread.currentThread().getName() + ",修改值成功,从1修改为2,版本为:" + stamp);
            } else {
                System.out.println(Thread.currentThread().getName() + ",修改值失败");
            }

            //将值从2修改为1
            value = stampedReference.getReference();
            stamp = stampedReference.getStamp();
            if (stampedReference.compareAndSet(value, 1, stamp, stamp + 1)) {
                System.out.println(Thread.currentThread().getName() + ",修改值成功,从2修改为1,版本为:" + stamp);
            } else {
                System.out.println(Thread.currentThread().getName() + ",修改值失败");
            }
        }, "thread2");

        //开启线程
        thread1.start();
        thread2.start();
    }
}

AtomicMarkableReference 可以理解为上面AtomicStampedReference的简化版,就是不关心修改过几次,仅仅关系是否修改过。因此变量mark是boolean类型,仅记录值是否修改。

import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.LockSupport;

/**
 * AtomicStampedReference解决ABA问题案例
 *
 * @Author warrior
 **/
public class AtomicStampedReferenceDemo {

    //定义AtomicStampedReference原子变量    Pair.reference值为1, Pair.stamp为1
    private static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(1, 1);

    public static void main(String[] args) {
        /**
         * 创建线程1,将原子变量值从1修改到3
         */
        Thread thread1 = new Thread(() -> {
            int value = stampedReference.getReference();
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + ",获取值为:" + value + ",版本为:" + stamp);

            //阻塞1s
            LockSupport.parkNanos(1000L);

            //将值从1修改为3
            if (stampedReference.compareAndSet(value, 3, stamp, stamp + 1)) {
                System.out.println(Thread.currentThread().getName() + ",修改值成功,从1修改为3,版本为:" + stamp);
            } else {
                System.out.println(Thread.currentThread().getName() + ",修改值失败,版本为:" + stamp);
            }
        }, "thread1");

        /**
         * 创建线程2,将原子变量值从1修改到2再修改到1
         */
        Thread thread2 = new Thread(() -> {
            int value = stampedReference.getReference();
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + ",获取值为:" + value + ",版本为:" + stamp);
            //将值从1修改为2
            if (stampedReference.compareAndSet(value, 2, stamp, stamp + 1)) {
                System.out.println(Thread.currentThread().getName() + ",修改值成功,从1修改为2,版本为:" + stamp);
            } else {
                System.out.println(Thread.currentThread().getName() + ",修改值失败");
            }

            //将值从2修改为1
            value = stampedReference.getReference();
            stamp = stampedReference.getStamp();
            if (stampedReference.compareAndSet(value, 1, stamp, stamp + 1)) {
                System.out.println(Thread.currentThread().getName() + ",修改值成功,从2修改为1,版本为:" + stamp);
            } else {
                System.out.println(Thread.currentThread().getName() + ",修改值失败");
            }
        }, "thread2");

        //开启线程
        thread1.start();
        thread2.start();
    }
}

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表