一、原子整数

J.U.C 并发包提供了:

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong

这几个类保证对包装的值的操作是原子操作,故线程安全。

以 AtomicInteger 为例

  1. //获取并自增(i=0,结果i=1,返回0),类似i++
  2. System.out.println(i.getAndIncrement());
  3. //自增并获取(i=1,结果i=2,返回2),类似++i
  4. System.out.println(i.incrementAndGet());
  5. //自减并获取(i=2,结果i=1,返回1),类似--i
  6. System.out.println(i.decrementAndGet());
  7. //获取并自减(i=1,结果i=0,返回1),类似i--
  8. System.out.println(i.getAndDecrement());
  9. //获取并加值(i=0,结果i=5,返回0)
  10. System.out.println(i.getAndAdd(5));
  11. //加值并获取(i=5,结果i=0,返回0)
  12. System.out.println(i.addAndGet(5));
  13. //更新并获取(i=5,结果i=10,返回50)
  14. System.out.println(i.updateAndGet(value->value*10));
  15. /*
  16. updateAndGet方法中传入的是函数式接口,可以直接表达式书写,
  17. 上例中的value->value*10,value是读取值,value*10是设置值
  18. */
  19. //获取并更新(i=5,结果i=50,返回5)
  20. System.out.println(i.getAndUpdate(value->value*10));

注意以上方法内部均用到原生的CAS实现,所以例子中取钱的实现可以直接改为:

  1. public void withdraw(Integer amout) {
  2. /*
  3. while(true){
  4. int prev=balance.get();//获取balance的最新值
  5. //要修改后的余额
  6. int next=prev-amout;
  7. //真正修改
  8. if(balance.compareAndSet(prev,next)){
  9. break;
  10. }//修改前和修改后的值传入
  11. }
  12. */
  13. balance.getAndAdd(-1*amount);
  14. }
  15. }

getAndAdd源码:

  1. public final int getAndAddInt(Object var1, long var2, int var4) {
  2. int var5;
  3. do {
  4. var5 = this.getIntVolatile(var1, var2);
  5. } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
  6. return var5;
  7. }

updateAndGet源码:

  1. public final int updateAndGet(IntUnaryOperator updateFunction) {
  2. int prev, next;
  3. do {
  4. prev = get();
  5. next = updateFunction.applyAsInt(prev);
  6. } while (!compareAndSet(prev, next));
  7. return next;
  8. }
  9. @FunctionalInterface
  10. public interface IntUnaryOperator {
  11. /**
  12. * Applies this operator to the given operand.
  13. *
  14. * @param operand the operand
  15. * @return the operator result
  16. */
  17. int applyAsInt(int operand);
  18. /**
  19. * Returns a composed operator that first applies the {@code before}
  20. * operator to its input, and then applies this operator to the result.
  21. * If evaluation of either operator throws an exception, it is relayed to
  22. * the caller of the composed operator.
  23. *
  24. * @param before the operator to apply before this operator is applied
  25. * @return a composed operator that first applies the {@code before}
  26. * operator and then applies this operator
  27. * @throws NullPointerException if before is null
  28. *
  29. * @see #andThen(IntUnaryOperator)
  30. */
  31. default IntUnaryOperator compose(IntUnaryOperator before) {
  32. Objects.requireNonNull(before);
  33. return (int v) -> applyAsInt(before.applyAsInt(v));
  34. }
  35. /**
  36. * Returns a composed operator that first applies this operator to
  37. * its input, and then applies the {@code after} operator to the result.
  38. * If evaluation of either operator throws an exception, it is relayed to
  39. * the caller of the composed operator.
  40. *
  41. * @param after the operator to apply after this operator is applied
  42. * @return a composed operator that first applies this operator and then
  43. * applies the {@code after} operator
  44. * @throws NullPointerException if after is null
  45. *
  46. * @see #compose(IntUnaryOperator)
  47. */
  48. default IntUnaryOperator andThen(IntUnaryOperator after) {
  49. Objects.requireNonNull(after);
  50. return (int t) -> after.applyAsInt(applyAsInt(t));
  51. }
  52. /**
  53. * Returns a unary operator that always returns its input argument.
  54. *
  55. * @return a unary operator that always returns its input argument
  56. */
  57. static IntUnaryOperator identity() {
  58. return t -> t;
  59. }
  60. }

updateAndGet方法传入的参数是 IntUnaryOperator 类变量,即将一个“操作”作为参数传入了方法,IntUnaryOperator是一个函数式接口,其中只有一个抽象方法等待实现:applyAsInt(int operand),传入一个int数,具体操作在实现类中定义。

方法的使用例子:

  1. //更新并获取(i=5,结果i=10,返回50)
  2. System.out.println(i.updateAndGet(value->value*10));

二、原子引用

共享变量的类型不只有基本类型,还有引用类型,即各个类的对象,所以原子引用的作用是:使得原引用类型的共享变量变为原子引用类型的共享变量,使得对该引用对象的操作都是原子操作,保证线程安全性。
原子引用与原子变量的用法一致,只不过因为是自定义的引用,对共享变量的访问操作需要使用原生CAS自定义。

  • AtomicReference
  • AtomicMarkableReference
  • AtomicStampedReference

注意:

引用类型的共享变量,如果更改的话是更改引用本身,相当于改变指针,而没有改变引用所指对象里的内容。

AtomicReference

银行取钱例子改进,共享变量银行余额从Integer变成Decimal,因为没有AtomicDecimal类,所以需要使用AtomicReference将Decimal类对象保护起来,注意AtomicReference类是泛型类,需要指定保护共享变量的原类型,用法如下例子中的:

private AtomicReference balance;

  1. import java.math.BigDecimal;
  2. import java.sql.SQLOutput;
  3. import java.util.ArrayList;
  4. import java.util.BitSet;
  5. import java.util.List;
  6. import java.util.concurrent.atomic.AtomicInteger;
  7. import java.util.concurrent.atomic.AtomicReference;
  8. public class TestAccount {
  9. public static void main(String[] args) {
  10. DecimalAccount.demo(new DecimalAccountCas(new BigDecimal("10000")));
  11. }
  12. }
  13. class DecimalAccountCas implements DecimalAccount{
  14. //是引用,修饰成原子引用
  15. private AtomicReference<BigDecimal> balance;
  16. public DecimalAccountCas(BigDecimal balance){
  17. this.balance=new AtomicReference<>(balance);
  18. }
  19. @Override
  20. public BigDecimal getBalance() {
  21. return balance.get();
  22. }
  23. @Override
  24. public void withdraw(BigDecimal amount) {
  25. while(true){
  26. //获取旧值
  27. BigDecimal prev = balance.get();
  28. BigDecimal next = prev.subtract(amount);
  29. if(balance.compareAndSet(prev,next)){
  30. break;
  31. }
  32. }
  33. }
  34. }
  35. interface DecimalAccount{
  36. //获取余额
  37. BigDecimal getBalance();
  38. //取款
  39. void withdraw(BigDecimal amout);
  40. /*
  41. * 方法内会启动1000个线程,每个线程作 -10元 的操作
  42. * 如果初始余额为 10000 那么线程正确执行的结果应为0元
  43. * */
  44. /*
  45. *jdk1.8之后支持接口中定义并实现静态方法
  46. * 本方法即1000个线程去一个银行对象取钱,1000个线程去调用getBalance方法与withdraw方法
  47. * */
  48. static void demo(DecimalAccount account){
  49. List<Thread> ts = new ArrayList<>();//创建线程集合
  50. for (int i=0;i<1000;i++){
  51. ts.add(new Thread(()->{
  52. account.withdraw(BigDecimal.TEN);
  53. }));
  54. }
  55. long start = System.nanoTime();
  56. ts.forEach(Thread::start);//每个线程执行start方法
  57. ts.forEach(t->{
  58. try{
  59. t.join();
  60. }catch (InterruptedException e){
  61. e.printStackTrace();
  62. }
  63. });
  64. long end= System.nanoTime();
  65. System.out.println(account.getBalance()+" cost:"+(end-start)/1000_000+"ms");
  66. }
  67. }

AtomicStampedReference

ABA问题

AtomicReferece引用保护的共享变量,无法察觉到最新值是否改变过,其compareAndSet方法仅仅是根据prev值与获取的最新值是否相等来判断CAS操作是否成功。比如共享变量ref原值为A,线程t1与线程t2将其改为A->B,B->A,那么对于主线程来讲,prev值为A,刚获取的ref的最新值也是A,CAS成功。但实际上值已经发生过改变。

如果希望只要有其它线程【动过了】共享变量,那么自己的cas就算失败,这时,仅比较值是不够的,需要再加一个版本号,引入AtomicStampedReference引用。

compareAndSet操作除了原先的期望值+修改值两个参数,还加入期望版本值与更改的版本值两个参数。

  • 如果stamp最新值与stamp预期值不同,代表预期值已经被修改过,那么cas直接失败;
  • 如果stamp最新值与stamp预期值相同,再去比较prev与内存中的最新值是否相同以判断cas能够成功; ```java import java.util.concurrent.atomic.AtomicStampedReference;

public class Test{

  1. //初始stamp值为0
  2. static AtomicStampedReference<String> ref= new AtomicStampedReference<>("A",0);
  3. public static void main(String[] args) throws InterruptedException {
  4. System.out.println("main start...");
  5. String prev= ref.getReference();//获取值
  6. //获取版本号
  7. int stamp = ref.getStamp();
  8. System.out.println("版本号:"+stamp);
  9. other();
  10. Thread.sleep(1000);
  11. System.out.println(ref.compareAndSet(prev,"C",stamp,stamp+1));
  12. }
  13. public static void other(){
  14. new Thread(()->{
  15. int stamp = ref.getStamp();//得到时间戳
  16. System.out.println("版本号:"+stamp);
  17. System.out.println(ref.compareAndSet(ref.getReference(),"B",stamp,stamp+1));
  18. },"t1").start();
  19. new Thread(()->{
  20. int stamp = ref.getStamp();//得到时间戳
  21. System.out.println("版本号:"+stamp);
  22. System.out.println(ref.compareAndSet(ref.getReference(),"A",stamp,stamp+1));
  23. },"t2").start();
  24. }

}

  1. <a name="INRbh"></a>
  2. ### AtomicMarkableReference
  3. 实际上一些应用场景并不关心值被更改了几次,而是关心是否被更改,那么即可以使用AtomicMarkableReference类
  4. 与AtomicStampedReference类似,在创建对象的时候直接指定标志位
  5. ```java
  6. //初始标志位的值为true值
  7. static AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("A",true);
  • 使用的时候如果值与设定的标志值相同,比如设定值为true,预期值也为true,那么cas操作成功;
  • 如果预期值与设定值不同,那么cas失败;