天天看点

JDK1.5提供的原子类原理及使用

JDK提供的原子类原理及使用

  • volatile只能保障可见性,不能保障原子性,如value++操作,就不是一个原子性操作,value++共分为以下三步操作(假如value的值是0):
    • 1、取出value的值为0;
    • 2、将value的值进行加一操作,得到一个新值为1;
    • 3、将新值1再赋值给变量value。
  • 假如线程1刚执行完了第二步,此时value的值依然为0,得到的新值为1,然后就轮到线程2执行。线程2执行第一步,取出value的值为0,再执行第二步,依然得到的新值为1,再执行第三步,将1赋值给value,执行完毕。此时线程1继续执行,又将1赋值给了value。所以在线程1和线程2执行完毕之后,执行了两次++操作,值本应该为2的value此时的值却为1,这时就出现了线程安全性问题。
  • 解决的办法就是让非原子性的++操作变成一个原子性操作即可,也就是说线程2必须等线程1三个步骤都执行完毕之后才能执行,通常我们都会使用synchronized同步代码块来保障++操作的原子性及内存的可见性
    public synchronized int getNext() {
    	return value++;
    }
               
  • 由于synchronized会导致线程上下文的切换,性能并不高,所以我们可以使用JDK提供的原子类的方式来保障内存的可见性及变量操作的原子性(注:原子类底层是volatile和CAS共同作用的结果)。
    public class MainTest {
    
    	private AtomicInteger value  = new AtomicInteger();
    	
    	public int getNext() {
    	    // value++
    		return value.getAndIncrement();
    	}
    
    	public static void main(String[] args) {
    
    		MainTest mainTest = new MainTest();
    
    		Runnable r = new Runnable() {
    
    			@Override
    			public void run() {
    				while (true) {
    					
    					String threadName = Thread.currentThread().getName();
    					System.out.println(threadName + " " + mainTest.getNext());
    					try {Thread.sleep();} catch (InterruptedException e) {}
    				}
    			}
    		};
    
    		new Thread(r).start();
    		new Thread(r).start();
    	}
    }
               
  • 原子类源码路径(java.util.concurrent.atomic)
    JDK1.5提供的原子类原理及使用
  • 原子类主要有以下的几种类型:
    • 原子更新基本类型(AtomicInteger、AtomicBoolean、AtomicLong)
      AtomicInteger value  = new AtomicInteger();
      value.getAndIncrement();
                 
    • 原子更新数组(AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray)
      int [] s = {,,,};
      AtomicIntegerArray a = new AtomicIntegerArray(s);
      // 将角标为2的值进行加1操作
      a.getAndIncrement();
      // 将角标为2的值进行加10操作
      a.getAndAdd(, );
                 
    • 原子更新引用类型(AtomicReference)
      // 对user这个引用的get和set,保障对引用的原子性操作
      AtomicReference<User> user = new AtomicReference<>();
                 
    • 原子更新字段(AtomicIntegerFieldUpdater,注:更新的字段不能使private,同时字段必须使用volatile进行修饰)
      // 对类字段的原子性操作
      AtomicIntegerFieldUpdater<User> old =  AtomicIntegerFieldUpdater.newUpdater(User.class, "old");
      User user = new User();
      System.out.println(old.getAndIncrement(user));
                 
  • 原子类实现原子性操作的原理
    • 通过乐观锁(CAS,Compare And Swap)来实现,所谓乐观锁就是每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
    • CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
    • 假如我们想对一个int型的变量value进行加1操作,总体的流程如下:
      • 1、先获取出value的值:int A = get();
      • 2、对value值进行加1操作:int B = A + 1;
      • 3、进行CAS操作:boolean flag = cas(A,B);
      • 4、假如flag为true,则返回 B,假如flag为false,则重复执行第一步操作,直到flag为true
    • AtomicInteger类中的getAndIncrement代码大致实现如下:
      public final int getAndIncrement() {
          int A, B;
          do {
              A = get();
              B = A + ;
          } while (!compareAndSet(A, B));
          return B
      }
                 
    • compareAndSet函数主要的操作如下:
      • 1、将A值和内存中的V值进行比较,若A != V,则返回false;
      • 2、若A == V,则将内存值更新为B,并返回true。
    • 从以上的步骤中我们也可以看出,整个过程的关键在于compareAndSet函数必须是一个原子性的操作,然而CAS也称为比较和交换,从字面的意义上我们可以得知CAS操作至少需要两步才能完成,所以compareAndSet函数也必须通过加锁来保障原子性。
    • 从JDK中的源码我们可以看到,compareAndSet并不是使用synchronized来保障原子性的,因为如果使用了synchronized的话,那么乐观锁将失去其自身的意义了。
    • compareAndSet函数调用的是unsafe.compareAndSwapInt方法。Unsafe类提供了硬件级别的原子操作,这个原子性的保证并不是靠java本身保证,而是靠一个更底层的与操作系统相关的特性实现(CPU锁),因此,JVM中的CAS的原子性是处理器保障的。
    • CAS存在着ABA的问题:
      • 因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
      • 解决的办法就是引入版本号,每修改一次则版本号加1,只有版本号和值都和预期值一直,compareAndSet函数才返回true。

继续阅读