java并发编程学习六——乐观锁CAS

一、CAS原理

CAS全称CompareAndSet或者CompareAndSwap,其中【比较-交换】的操作是原子的,下面先一个具体的案例

1.1 无锁保护共享变量

定义一个接口

interface Acount {

    void withdraw(int amount);

    Integer getBalance();
	//设置余额为10000,一千个线程每个线程减少10,正确结果应该为0
    static void demo(Acount acount) {
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            threadList.add(new Thread(() -> acount.withdraw(10)));
        }
        long start = System.currentTimeMillis();
        threadList.forEach(Thread::start);

        threadList.forEach(thread -> {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.currentTimeMillis();
        
        System.out.printf("执行时间:%d,余额:%d", end - start, acount.getBalance());
    }
}

1.1.1 不安全模式实现

不安全的实现

class AcountUnsafe implements Acount {
    private Integer balance;

    public AcountUnsafe(Integer balance) {
        this.balance = balance;
    }

    @Override
    public void withdraw(int amount) {
        this.balance -= amount;
    }

    public Integer getBalance() {
        return balance;
    }

    public void setBalance(Integer balance) {
        this.balance = balance;
    }
}

测试

public class Test1 {
    public static void main(String[] args) {
        Acount acount = new AcountUnsafe(10000);
        Acount.demo(acount);
    }
}

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

1.1.2 有锁安全实现

给withdraw加锁即可

 @Override
    public void withdraw(int amount) {
        synchronized(this){
            this.balance -= amount;
        }
    }

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

1.1.3 无锁安全实现

无锁采用原子整数AtomicInteger来实现

class AcountSafe implements Acount {
    private AtomicInteger balance;

    public AcountSafe(AtomicInteger balance) {
        this.balance = balance;
    }

    @Override
    public void withdraw(int amount) {
        while (true) {
            int prev = this.balance.get();
            int value = prev - amount;
            // compareAndSet是JVM提供的本地方法,该方法具有原子性
            if (this.balance.compareAndSet(prev, value)) {
                break;
            }
        }
    }

    public Integer getBalance() {
        return balance.get();
    }

}

结果
在这里插入图片描述
比较执行时间发现,无锁比有锁实现要快一点

1.2 CAS工作方式

先分析下上述代码的时序图
在这里插入图片描述
CAS底层使用的lock compxchg指令(X86架构),在单核CPU和多核CPU下都能保证【比较-交换】的原子性。CAS必须借助volatile才能读到共享变量的最新值,从而完成【比较-交换】的效果,来看看AtomicInteger的源码,其中初始值value就是用volatile修饰的
在这里插入图片描述
注意,volatile只能保证可见性,让其他线程看到最新值,但不能保证原子性即不能解决多个线程的指令交错问题。

1.3 CAS的效率和特点

CAS的作用是用无锁来实现多线程对共享变量操作的安全性,由于需要不停重试它也不是一定能提高效率。
CAS的效率:

  • 无锁的情况下,即使重试失败,线程仍然在高速运行。synchronized加锁会让线程进入阻塞,发生上下文切换。
  • 线程上下文切换,好比赛车在跑动高速运行时,需要先刹车减速停下之后再被唤醒之后重新启动、加速,代价比较大。
  • 无锁情况下,线程要保持运行,需要CPU支援。在单核CPU下,线程不加锁也会由于时间片使用完,发生上下文切换。因此,CAS需要在多核下才能发挥优势,而且线程数最好不要超过CPU核数。

CAS的特点:

  • CAS基于乐观锁的思维实现,不怕别人修改共享变量,修改了没关系,自己再重试
  • synchronized基于悲观锁的思维,加锁之后不允许别人修改共享变量,除非自己修改完释放锁,别人才有机会
  • CAS体现的是无锁并发、无阻塞并发。由于没有加锁,不会发生阻塞,从而提高效率。但是在竞争激烈的情况下,会发生大量的无效重试,反而会影响效率。

二、原子整数

2.1 AtomicInteger、AtomicBoolean、AtomicLong

AtomicInteger、AtomicBoolean、AtomicLong,这三个比较类似,都可以看做是对一个整数的封装。

public class AtomicIntegerTest {
    public static void main(String[] args) {
        AtomicInteger i = new AtomicInteger(0);
        //自增等价于++i
        System.out.println(i.incrementAndGet());
        //i++
        System.out.println(i.getAndIncrement());
        System.out.println(i.get());
        //类似的还有--i,i--

        //增加指定的数
        System.out.println(i.addAndGet(3));
        System.out.println(i.getAndAdd(3));
        System.out.println(i.get());

        //其他计算,乘法等复杂运算
        System.out.println(i.updateAndGet(x -> x * 2 - 1));
        System.out.println(i.getAndUpdate(x -> x * 2 - 1));
        System.out.println(i.get());

        //自己实现updateAndGet
        System.out.println(updateAndGet(i,x->(x-1)*2));
    }

    private static AtomicInteger updateAndGet(AtomicInteger i, IntUnaryOperator intUnaryOperator) {
        int prev;
        do {
            prev = i.get();
        } while (!i.compareAndSet(prev, intUnaryOperator.applyAsInt(prev)));
        return i;
    }
}

2.2 Unsafe模拟AtomicInteger

public class MyAtomicIntegerTest implements Acount {

    public static void main(String[] args) {
        Acount test = new MyAtomicIntegerTest(10000);
        Acount.demo(test);
    }

    MyAtomicInteger balance;

    public MyAtomicIntegerTest(int i) {
        this.balance = new MyAtomicInteger(i);
    }

    @Override
    public void withdraw(int amount) {
        balance.incrementAndGet(-amount);
    }

    @Override
    public Integer getBalance() {
        return balance.get();
    }
}

class MyAtomicInteger {
    volatile int value;

    private static Unsafe UNSAFE;

    private static long valueOffset;

    static {
        try {
            //利用反射获取Unsafe的实例
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            UNSAFE = (Unsafe) field.get(null);
            //获取字段value相对类对象内存开始位置的偏移量
            valueOffset = UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public MyAtomicInteger(int value) {
        this.value = value;
    }

    public int get() {
        return value;
    }

    public void incrementAndGet(int i) {
        while (true) {
            if (UNSAFE.compareAndSwapInt(this, valueOffset, this.get(), this.get() + i)) {
                break;
            }
        }
    }
}

测试结果:
在这里插入图片描述

三、原子引用

3.1 AtomicReference

使用AtomicReference实现Account

public class AtomicReferenceTest {
    public static void main(String[] args) {
        Acount acount = new AccountBigDecimalSafe(new BigDecimal("10000"));
        Acount.demo(acount);
    }
}

class AccountBigDecimalSafe implements Acount {
    private AtomicReference<BigDecimal> balance;

    AccountBigDecimalSafe(BigDecimal balance) {
        this.balance = new AtomicReference<>(balance);
    }

    @Override
    public void withdraw(int amount) {
        while (true) {
            BigDecimal prev = balance.get();
            BigDecimal next = prev.subtract(new BigDecimal(amount));
            if (balance.compareAndSet(prev, next)) {
                break;
            }
        }
    }

    @Override
    public Integer getBalance() {
        return balance.get().intValue();
    }
}

3.2 ABA问题

字符串A,先改成了B又改回A,主线程发现字符串还是A,就改成了C。一般ABA问题不会有什么影响,但在实际工作中还是注意。

public class AbaTest {
    public static void main(String[] args) {
        AtomicReference atomicReference = new AtomicReference("A");
        System.out.println(atomicReference);
        new Thread(() -> {
            if (atomicReference.compareAndSet("A", "B")) {
                System.out.println(atomicReference);
            }
            if (atomicReference.compareAndSet("B", "A")) {
                System.out.println(atomicReference);
            }
        }).start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if (atomicReference.compareAndSet("A", "C")) {
            System.out.println(atomicReference);
        }
    }
}

3.3 AtomicMarkableReference

可以使用AtomicMarkableReference解决ABA问题

public class AbaTest2 {
    public static void main(String[] args) {
        AtomicMarkableReference<String> atomicReference = new AtomicMarkableReference("A",false);
        System.out.println(atomicReference.getReference());
        new Thread(() -> {
            if (atomicReference.compareAndSet("A", "B",false,true)) {
                System.out.println(atomicReference.getReference());
            }
            if (atomicReference.compareAndSet("B", "A",false,true)) {
                System.out.println(atomicReference.getReference());
            }
        }).start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if (atomicReference.compareAndSet("A", "C",false,true)) {
            System.out.println(atomicReference.getReference());
        }
    }
}

AtomicMarkableReference增加了是否改动过的标记,只要改动过,并被标记上,后面的改动就无法生效。

四、原子累加器

原子累加器,指的是累加操作是原子的,java8之前只能使用原子整数类的incrementAndGet方法,java8新增了LongAdder(并发大神狗哥,Doug Lea编写),专门用于累加操作,效率提升5倍。

public class LongAddrTest {

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            addr(() -> new AtomicLong(0), (AtomicLong::incrementAndGet));
        }
        for (int i = 0; i < 5; i++) {
            addr(LongAdder::new, LongAdder::increment);
        }

    }

    static <T> void addr(Supplier<T> supplier, Consumer<T> action) {
        T t = supplier.get();
        List<Thread> threads = new ArrayList<>(4);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 4; i++) {
            threads.add(new Thread(() -> {
                for (int j = 0; j < 500000; j++) {
                    action.accept(t);
                }
            }));
        }
        threads.forEach(Thread::start);
        threads.forEach(thread -> {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - start));

    }
}

测试结果:
在这里插入图片描述
LongAddr性能提升的原因是拆分了多个累加单元[cell0]…,当竞争激烈的时候,累加分散到多个cell减少失败重试,最后将结果汇总,最终减少竞争提高效率。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值