之前我们临界区访问的时候,通常使用加锁来实现。
看这个多线程转账的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package com.redisc;import lombok.extern.slf4j.Slf4j;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import java.util.ArrayList;import java.util.List;import java.util.concurrent.atomic.AtomicInteger;@Slf 4j(topic = "c.Test" )public class Run { public static void main (String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException { Account account = new AccountCas(10000 ); Account.demo(account); } } class AccountCas implements Account { private AtomicInteger balance; public AccountCas (int balance) { this .balance = new AtomicInteger(balance); } @Override public Integer getBlance () { return balance.get(); } @Override public void withdraw (Integer amount) { while (true ) { int prev = balance.get(); int next = prev - amount; if (balance.compareAndSet(prev, next)) { break ; } } } } interface Account { Integer getBlance () ; void withdraw (Integer amount) ; static void demo (Account account) { List<Thread> ts = new ArrayList<>(); for (int i = 0 ; i < 1000 ; i++) { ts.add(new Thread(() -> { account.withdraw(10 ); })); } ts.forEach(Thread::start); ts.forEach(t -> { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(account.getBlance()); } }
这里面的关键点是
1 2 3 4 5 6 7 8 9 10 11 @Override public void withdraw (Integer amount) { while (true ) { int prev = balance.get (); int next = prev - amount; if (balance.compareAndSet(prev, next)) { break ; } } }
最主要的方法是 compareAndSet
,它的简称就是 CAS
,它必须是原子操作。
其原理为
图中,线程1
记录了 cas(100,90)
就是它之前的数据是 100
,现在要改成 90
,但是,线程2
在 cas
执行之前已经修改为 90
了,和之前的数据不一样了。所以,cas(100,90)
修改失败。
因为,每次 cas
都需要获取最新的值,所以,为了保证变量的可见性,需要使用 volatile
修饰。