12大原子更新类 - morris131/morris-book GitHub Wiki

12大原子更新类

  • 基本类:AtomicInteger、AtomicLong、AtomicBoolean;
  • 引用类型:AtomicReference、AtomicStampedRerence、AtomicMarkableReference;
  • 数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
  • 属性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

基本使用

12大原子更新类的使用大同小异,下面以AtomicInteger为例。AtomicInteger的常用方法如下:

  • int addAndGet(int delta):以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
  • boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
  • int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值。
  • int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。
  • int incrementAndGet():以原子方式将当前值加1后返回
package com.morris.atomic;

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest {

	private static AtomicInteger atomicInteger = new AtomicInteger();

	public static void main(String[] args) throws InterruptedException {
		final Thread[] threads = new Thread[10];
		for (int i = 0; i < 10; i++) {
			final int num = i;
			threads[i] = new Thread() {
				public void run() {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					int now = atomicInteger.incrementAndGet();
					System.out.println("我是线程:" + num + ",我得到值了,增加后的值为:" + now);
				}
			};
			threads[i].start();
		}
		for (Thread t : threads) {
			t.join();
		}
		System.out.println("最终运行结果:" + atomicInteger.get());
	}
}

源码实现

    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

Atomic包提供了3种基本类型的原子更新,但是Java的基本类型里还有char、float和double 等。那么如何原子的更新其他的基本类型呢?Atomic包里的类基本都是使用Unsafe实现的,让我们一起看一下Unsafe的源码。

Unsafe的源码:http://www.docjar.com/html/api/sun/misc/Unsafe.java.html

public final native boolean compareAndSwapObject(Object o,long offset,Objectexpected,Object x);
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);

参数解释:

  • 参数1:对象所在的类本身的对象(一般这里是对一个对象的属性做修改,才会出现并发,所以该对象所存在的类也是有一个对象的)
  • 参数2:这个属性在这个对象里面的相对便宜量位置,其实对比时是对比内存单元,所以需要属性的起始位置,而引用就是修改引用地址(根据OS、VM位数和参数配置决定宽度一般是4-8个字节),int就是修改相关的4个字节,而long就是修改相关的8个字节。 获取偏移量也是通过unsafe的一个方法:objectFieldOffset(Fieldfield)来获取属性在对象中的偏移量;静态变量需要通过:staticFieldOffset(Field field)获取,调用的总方法是:fieldOffset(Fieldfield)
  • 参数3:修改的引用的原始值,用于对比原来的引用和要修改的目标是否一致。
  • 参数4:修改的目标值,要将数据修改成什么。

对象的引用进行对比后交换,交换成功返回true,交换失败返回false,这个交换过程完全是原子的,在CPU上计算完结果后,都会对比内存的结果是否还是原先的值,若不是,则认为不能替换,因为变量是volatile类型所以最终写入的数据会被其他线程看到,所以一个线程修改成功后,其他线程就发现自己修改失败了。

通过代码,我们发现Unsafe只提供了3种CAS方法:compareAndSwapObject、compare- AndSwapInt和compareAndSwapLong,再看AtomicBoolean源码,发现它是先把Boolean转换成整 型,再使用compareAndSwapInt进行CAS,所以原子更新char、float和double变量也可以用类似 的思路来实现。

ABA问题

package com.morris.atomic;

import java.util.concurrent.atomic.AtomicInteger;

public class ABATest {

	private static AtomicInteger atomicInteger = new AtomicInteger(0);

	public static void main(String[] args) throws InterruptedException {
		for (int i = 0; i < 10; i++) {
			final int num = i;
			new Thread() {
				public void run() {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if (atomicInteger.compareAndSet(0, 1)) {
						System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");
					}
				}
			}.start();
		}

		new Thread() {
			public void run() {
				while (!atomicInteger.compareAndSet(1, 0))
					;
				System.out.println("已经改为原始值!");
			}
		}.start();
	}

}

运行结果如下:

已经改为原始值!
我是线程:3,我获得了锁进行了对象修改!
我是线程:6,我获得了锁进行了对象修改!

可以发现,有两个线程修改了这个值,我们是想那一堆将0改成1的线程仅有一个成功。此时我们通过类来AtomicStampedReference解决这个问题。

package com.morris.atomic;

import java.util.concurrent.atomic.AtomicStampedReference;

public class ABADealTest {
	private static AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<Integer>(0, 1);

	public static void main(String[] args) throws InterruptedException {
		for (int i = 0; i < 10; i++) {
			final int num = i;
			final int stamp = atomicInteger.getStamp();
			new Thread() {
				public void run() {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if (atomicInteger.compareAndSet(0, 1, stamp, stamp + 1)) {
						System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");
					}
				}
			}.start();
		}

		new Thread() {
			public void run() {
				int stamp = atomicInteger.getStamp();
				while (!atomicInteger.compareAndSet(1, 0, stamp, stamp + 1)) {
				}
				System.out.println("已经改为原始值!");
			}
		}.start();
	}
}

⚠️ **GitHub.com Fallback** ⚠️