Java高并发--原子变量

1 概述

原子变量最主要的一个特点就是所有的操作都是原子的,synchronized关键字也可以做到对变量的原子操作。只是synchronized的成本相对较高,需要获取锁对象,释放锁对象,如果不能获取到锁,还需要阻塞在阻塞队列上进行等待。而如果单单只是为了解决对变量的原子操作,建议使用原子变量。

关于原子变量的介绍,主要涉及以下内容:

  1. 原子变量的基本概念
  2. 通过AtomicInteger了解原子变量的基本使用
  3. 通过AtomicInteger了解原子变量的基本原理
  4. AtomicReference的基本使用
  5. 使用FieldUpdater操作非原子变量的字段属性
  6. 经典的ABA问题的解决

2 原子变量的基本概念

原子变量保证了该变量的所有操作都是原子的,不会因为多线程的同时访问而导致脏数据的读取问题。我们先看一段synchronized关键字保证变量原子性的代码:

public class Counter {
    private int count;
    public synchronized void addCount(){
        this.count++;
    }
}

简单的count++操作,线程对象首先需要获取到Counter类实例的对象锁,然后完成自增操作,最后释放对象锁。整个过程中,无论是获取锁还是释放锁都是相当消耗成本的,一旦不能获取到锁,还需要阻塞当前线程等等。对于这种情况,我们可以将count变量声明成原子变量,那么对于count的自增操作都可以以原子的方式进行,就不存在脏数据的读取了。Java给我们提供了以下几种原子类型:

  1. AtomicInteger和AtomicIntegerArray:基于Integer类型
  2. AtomicBoolean:基于Boolean类型
  3. AtomicLong和AtomicLongArray:基于Long类型
  4. AtomicReference和AtomicReferenceArray:基于引用类型

在本文的余下内容中,我们将主要介绍AtomicInteger和AtomicReference两种类型,AtomicBoolean和AtomicLong的使用和内部实现原理几乎和AtomicInteger一样。

3 AtomicInteger的基本使用

构造函数如下:

/**
 * Creates a new AtomicInteger with the given initial value.
 *
 * @param initialValue the initial value
 */
public AtomicInteger(int initialValue) {
	value = initialValue;
}

/**
 * Creates a new AtomicInteger with initial value {@code 0}.
 */
public AtomicInteger() {
}

可以看到,我们在通过构造函数构造AtomicInteger原子变量的时候,如果指定一个int的参数,那么该原子变量的值就会被赋值,否则就是默认的数值0。也有获取和设置这个value值的方法:

/**
 * Gets the current value.
 *
 * @return the current value
 */
public final int get() {
	return value;
}

/**
 * Sets to the given value.
 *
 * @param newValue the new value
 */
public final void set(int newValue) {
	value = newValue;
}

我们可以看见上面两个方法并不是原子的,所以针对原子变量使用,我们很少使用这两个方法,而是使用下面的一些方法:

/**
 * 获取旧值并设置新值
 *
 * @param newValue the new value
 * @return the previous value
 */
public final int getAndSet(int newValue) {
	return unsafe.getAndSetInt(this, valueOffset, newValue);
}

/**
 * 当要设置的值和期望的值相同的时候就设置成新值
 *
 * @param expect the expected value
 * @param update the new value
 * @return {@code true} if successful. False return indicates that
 * the actual value was not equal to the expected value.
 */
public final boolean compareAndSet(int expect, int update) {
	return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
	
 /**
 * 获取当前的value值并自增一
 *
 * @return the previous value
 */
public final int getAndIncrement() {
	return unsafe.getAndAddInt(this, valueOffset, 1);
}

/**
 * 获取当前的value值并自减一
 *
 * @return the previous value
 */
public final int getAndDecrement() {
	return unsafe.getAndAddInt(this, valueOffset, -1);
}

/**
 * 获取当前的value值并增加delta
 *
 * @param delta the value to add
 * @return the previous value
 */
public final int getAndAdd(int delta) {
	return unsafe.getAndAddInt(this, valueOffset, delta);
}

使用原子变量实现计数器:

public class Test {
	public static void main(String[] args) throws InterruptedException {
		Thread[] threads = new Thread[100];

		for(int i = 0; i < 100;i++){
			threads[i] = new MyThread();
			threads[i].start();
		}

		for(int i = 0; i < 100;i++){
			threads[i].join();
		}

		System.out.println(MyThread.value);
	}
}

class MyThread extends Thread{
    public static AtomicInteger value = new AtomicInteger();

    @Override
    public void run(){
        try {
            Thread.sleep(100);

            //原子自增
            value.incrementAndGet();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

我们可以看见这种实现计数器的操作比使用synchronized要简单很多,而且效率较高。

4 AtomicInteger的内部基本原理

通过查看源码我们可以看见value值前面的变量修饰如下:

private volatile int value;

我们可以看见通过volatile关键字进行了修饰,所以value具有可见性和禁止指令重排的特点。查看compareAndSet方法:

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

我们可以看见这个方法主要依靠了native方法来实现的,这个方法就相当于一个CAS操作,比较当前的值和期望值是否相同,如果相同就设置成功,并且返回true,否则直接放回false。所以我们可以看见其实内部的实现主要是依靠unsafe里面的方法。

5 AtomicReference的基本使用

/**
 * Creates a new AtomicReference with the given initial value.
 *
 * @param initialValue the initial value
 */
public AtomicReference(V initialValue) {
	value = initialValue;
}

/**
 * Creates a new AtomicReference with null initial value.
 */
public AtomicReference() {
}

我们可以看见AtomicReference是基于泛型来实现的,依然可以看见里面的value被volitile修饰着。 

private volatile V value;

关于它的一些方法我们可以查看源码。

6 使用FieldUpdater操作非原子变量的字段属性 

FieldUpdater允许我们不必将字段设置为原子变量,利用反射直接以原子方式操作字段。例如:

public class Test {
	public static void main(String[] args) throws InterruptedException {
		Counter2 counter2 = new Counter2();
		for(int i = 0; i < 100;i++){
			counter2.addCount();
		}

		System.out.println(counter2.getCount());
	}
}

class Counter2{

    private volatile int count;

    public int getCount(){
        return count;
    }

    public void addCount(){
        AtomicIntegerFieldUpdater<Counter2> updater = AtomicIntegerFieldUpdater.newUpdater(Counter2.class,"count");
        updater.getAndIncrement(this);
    }

}

后我们创建一百个线程随机调用同一个Counter对象的addCount方法,无论运行多少次,结果都是一百。这种方式实现的原子操作,对于被操作的变量不需要被包装成原子变量,但是却可以直接以原子方式操作它的数值。

7 经典的ABA问题

我们的原子变量都依赖一个核心的方法,那就是CAS。这个方法最核心的思想就是,更改变量值之前先获取该变量当前最新的值,然后在实际更改的时候再次获取该变量的值,如果没有被修改,那么进行更改,否则循环上述操作直至更改操作完成。假如一个线程想要对变量count进行修改,实际操作之前获取count的值为A,此时来了一个线程将count值修改为B,又来一个线程获取count的值为B并将count修改为A,此时第一个线程全然不知道count的值已经被修改两次了,虽然值还是A,但是实际上数据已经是脏的。这就是典型的ABA问题,一个解决办法是,对count的每次操作都记录下当前的一个时间戳,这样当我们原子操作count之前,不仅查看count的最新数值,还记录下该count的时间戳,在实际操作的时候,只有在count的数值和时间戳都没有被更改的情况之下才完成修改操作。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        int count = 0;
        int stamp = 1;
        AtomicStampedReference reference = new AtomicStampedReference(count,stamp);
        int next = 2;
        System.out.println(reference.compareAndSet(count,next,stamp,stamp+1));
        System.out.println(reference.getReference());
    }
}

AtomicStampedReference的CAS方法要求传入四个参数,该方法的内部会同时比较count和stamp,只有这两个值都没有发生改变的前提下,CAS才会修改count的值。

上述我们介绍了有关原子变量的最基本内容,最后我们比较下原子变量和synchronized关键字的区别。

从思维模式上看,原子变量代表一种乐观的非阻塞式思维,它假定没有别人会和我同时操作某个变量,于是在实际修改变量的值的之前不会锁定该变量,但是修改变量的时候是使用CAS进行的,一旦发现冲突,继续尝试直到成功修改该变量。而synchronized关键字则是一种悲观的阻塞式思维,它认为所有人都会和我同时来操作某个变量,于是在将要操作该变量之前会加锁来锁定该变量,进而继续操作该变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值