BiBi - 并发编程 -4- 原子操作

2019-12-10  本文已影响0人  奋飞的蜗牛ing

From:Java并发编程的艺术

1. 实现原子操作的方法

第一个机制:通过【总线锁】保证原子性。处理器提供一个LOCK#信号,当一个处理器在总线上输出此信号时,其它处理器的请求将被阻塞住,那么该处理器可以独占共享内存。
总线锁把CPU和内存之间的通信锁住了,这使得锁定期间,其它处理器不能操作其它内存地址的数据,所以总线锁定的开销比较大,目前处理器在某些场合下使用第二个机制=>缓存锁,来替换总线锁来进行优化。【如volatile】

第二个机制:通过【缓存锁】保证原子性。在同一时刻,我们只需要保证对某个内存地址的操作是原子性即可。
内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性。

2. CAS

CAS【compare and swap】需要输入两个数值,一个旧值和一个新值,先比较旧值没有发生变化,才交换成新值。

通过循环CAS【自旋CAS】实现原子操作
public void safeCount(){
  for( ; ; ){
    int i = atomicI.get();
    boolean b = atomicI.compareAndSet(i, ++i);
    if (b){
      break;
    }
  }
}
CAS实现原子操作的问题

1)ABA问题。一个值原来是A,变成了B,又变成了A,那么使用CAS时进行检查时会发现它的值没有发生变化。解决思路:使用版本号,在变量前面追加上版本号,每次变量更新使版本号加1,那么A>B>A就变成了1A>2B<3A。Java1.5提供了类AtomicStampedReference来解决ABA问题。
2)循环时间长开销大。自旋CAS如果长时间不成功,给CPU带来很大开销。
3)只能保证一个共享变量的原子操作。可以通过把多个共享变量合并成一个共享变量来改进。Java1.5提供AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里进行CAS操作。

提示:除了偏向锁,JVM其它锁的实现方式都用了循环CAS,即使用CAS的方式来获取/释放锁。

3. 原子类 - Atomic

原子更新基础类型

AtomicBoolean、AtomicInteger、AtomicLong。
分析AtomicInteger源码:

public class AtomicInteger extends Number implements java.io.Serializable {
  private static final long serialVersionUID = 6214790243416807050L;

  private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); //包装类
  private static final long VALUE;

  static {
    try {
      VALUE = U.objectFieldOffset
          (java.util.concurrent.atomic.AtomicInteger.class.getDeclaredField("value"));
    } catch (ReflectiveOperationException e) {
      throw new Error(e);
    }
  }

  private volatile int value; //volatile类型

  public AtomicInteger(int initialValue) {
    value = initialValue;
  }

  public final int getAndIncrement() {
    return U.getAndAddInt(this, VALUE, 1);
  }

  public final boolean compareAndSet(int expect, int update) {
    return U.compareAndSwapInt(this, VALUE, expect, update);
  }

  public final boolean compareAndSet(int expect, int update) {
    return U.compareAndSwapInt(this, VALUE, expect, update);
  }
}

Atomic包里的类都是使用Unsafe实现的包装类。Unsafe中提供了3种CAS方法:compareAndSwapObject、compareAndSwapInt、compareAndSwapLong。

原子更新数组

AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray。
AtomicIntegerArray使用例子:

package com.ljg;

import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArrayTest {
  static int[] value = new int[] { 1, 2 };
  static AtomicIntegerArray ai = new AtomicIntegerArray(value);

  public static void main(String[] args) {
    ai.getAndSet(0, 3);
    System.out.println(ai.get(0)); //3
    System.out.println(value[0]); //1,保持不变
  }
}

可见AtomicIntegerArray对内部的数组元素修改时,不会影响传入的数组。

原子更新引用类型

因为AtomicInteger只能更新一个变量,如果要原子更新多个变量,就需要使用原子更新对象引用。
AtomicReference、AtomicReferenceFieldUpdater、AtomicMarkableReference。
AtomicReference使用例子:

package com.ljg;

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceTest {
  public static AtomicReference<User> atomicRef = new AtomicReference<User>();

  public static void main(String[] args) {
    User user = new User("lili", 15);
    atomicRef.set(user);
    User updateUser = new User("liLei", 18);
    atomicRef.compareAndSet(user, updateUser);
    System.out.println(atomicRef.get().getName());
    System.out.println(atomicRef.get().getAge());
  }
}
原子更新字段类

AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicStampedReference。
想要原子地更新某个类里的某个字段,需要该字段属性必须使用public volatile修饰符。
AtomicIntegerFieldUpdater使用例子:

package com.ljg;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterTest {
 //创建更新器
 private static AtomicIntegerFieldUpdater a =
     AtomicIntegerFieldUpdater.newUpdater(User.class, "age");

 public static void main(String[] args) {
   User conan = new User("conan", 10);
   System.out.println(a.getAndIncrement(conan)); //10
   System.out.println(a.get(conan)); //11
 }
}
上一篇 下一篇

猜你喜欢

热点阅读