Java并发编程:AtomicInteger&CAS

很多情况下我们只需要一个简单的、高效的、线程安全的递增递减方案,而Java中++i或--i并不是线程安全的,但是java.util.concurrent包中提供原子(Atomic) 操作的类,今天我们就来学习它最基本的AtomicInteger。
 
以下是本文包含的知识点:
1.什么是原子操作
2.AtomicInteger用法
3.CAS介绍
4.AtomicIntegerArray/AtomicIntegerFieldUpdater<T> 介绍
 
一、什么是原子操作
通常情况下,在Java里面,++i或者--i不是线程安全的,这里面有三个独立的操作:获得变量当前值,为该值+1/-1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁(synchronized)才能保证读-改-写这三个操作时“原子性”的。

 

来看下面的例子:
public class AtomicTest {

	public static int num = 0;
	
	public static void increment(){
		num++;
	}
	
	public static void main(String[] args) {
		Thread[] threads = new Thread[20];
		for(int i=0; i< threads.length; i++){
			threads[i] = new Thread(new Runnable() {
				public void run() {
					for(int i=0;i<10000;i++){
						increment();
					}
				}
			});
			threads[i].start();
		}
		
		//等待所有累加线程都结束
		while(Thread.activeCount() > 1){
			Thread.yield();
		}
		System.out.println("num="+num);
	}
}
  这段代码发起20个线程,每个线程对变量num进行10000次自增操作,如果这段代码能够正确并发的话,最后输出的结果应该是200000。结果运行之后,发现每次执行它都小于200000,这是为什么呢?
问题就出在自增运算num++中,它其实包含了三个独立的操作:获得变量当前值,为该值+1/-1,然后写回新的值。这三个操作又不具备原子性操作,一个线程在执行的时候,有可能被其它线程打断,这时num的值就不安全,同时被多个线程共享,修改。
要解决这个问题,其实很简单,加锁就可以了,用synchronized或lock都行。只需要给 increment()加上锁即可:
public synchronized static void increment(){
     num++;
}
除了使用同步加锁,JDK5以后还提供了内置的API来解决原子性的自增,自减操作,下面我们来看最基本的AtomicInteger的用法。
 
 
二、AtomicInteger用法
AtomicInteger是java.util.concurrent.atomic包中最基本的原子操作类,即int类型的自增、自减原子性操作,我们来看用它实现上面的num自增操作:
public class AtomicIntegerTest {
	
	private static AtomicInteger num = new AtomicInteger(0);
	
	public static void increment(){
		num.incrementAndGet();
	}
	
	public static void main(String[] args) {
		Thread[] threads = new Thread[20];
		for(int i=0; i< threads.length; i++){
			threads[i] = new Thread(new Runnable() {
				public void run() {
					for(int i=0;i<100000;i++){
						increment();
					}
				}
			});
			threads[i].start();
		}
		//等待所有累加线程都结束
		while(Thread.activeCount() > 1){
			Thread.yield();
		}
		System.out.println("num="+num);
	}

}
  执行后,每次运行结果都是200000。而且还不用手动加锁,因为它本身实现的就是线程安全的原子性操作。
AtomicInteger除了 incrementAndGet()方法,它还提供其它自增,自减的方法:
decrementAndGet()自减,相当于--i,返回修改后的值
getAndIncrement()自增, 相当于++i,返回修改前的值
getAndDecrement() 自减,相当于--i,返回修改 的值
getAndSet() 设置值, 返回修改 的值
compareAndSet()比较赋值,修改成功返回true,否则返回false
还有其它方法,这些方法都是原子性操作,都是线程安全的。那问题来了,AtomicInteger是怎么保证原子性操作的呢,其实它是利用处理器指令比较并交换(Compare-and-Swap,简称CAS)来实现的。
 
三、CAS介绍
CAS指令需要3个操作数,分别是内存位置(Java中可理解为内存地址,用V表示),旧的预期值(用A表示)和新值(用B表示)。指令执行时,当且仅当V符合旧的预期值A时,处理器用新值B更新V的值,否则不更新。
在JDK5以后,Java程序才可以使用CAS操作,具体由Unsafe类来实现,并且用户程序无法直接调用,我们可以通过Java API来间接使用它,如J.U.C包里的整数原子类AtomicInteger,其中的compareAndSet()和getAndIncrement都是使用了Unsafe类的CAS操作。
我们来看 AtomicInteger的 incrementAndGet()源码:
/**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }
incrementAndGet()方法在一个无限循环中,不断尝试将一个比自己大1的值赋给自己,如果失败了,那说明在执行“获取-设置”操作的时候已经有了修改,于是再次循环进行下一次操作,直到设置成功为止。
ABA问题:尽管CAS看起很美,但显然这种操作无法涵盖互斥同步的所有使用场景,并且CAS从语义上说并不完美,存在这样一个漏洞:如果一个变量初次读取的时候是A值,并且在准备赋值检查它是仍然是A值,那我们就说它的值没有被其它线程改变过吗?如果在这段期间它的值曾经改成了B,后来又改回成A,那CAS操作就会误以为它从来都没变过,这个漏洞称为CAS操作的ABA问题。如果要解决ABA问题,请使用互斥同步。
 
四、AtomicIntegerArray/AtomicIntegerFieldUpdater<T> 介绍
AtomicInteger和AtomicLong、AtomicBoolean、AtomicReference差不多,这里就不介绍了。
 
AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray的API类似,为数组的原子操作类,以AtomicIntegerArray为例来看下:
int addAndGet(int i, int delta) 以原子方式将给定值与索引 i 的元素相加
boolean compareAndSet(int i, int expect, int update)  如果当前值  ==  预期值,则以原子方式将位置  i  的元素设置为给定的更新值
int decrementAndGet(int i)以原子方式将索引 i 的元素减 1
int get(int i)获取位置 i 的当前值
int getAndAdd(int i, int delta)以原子方式将给定值与索引 i 的元素相加
int getAndDecrement(int i)以原子方式将索引 i 的元素减 1
int getAndIncrement(int i) 以原子方式将索引 i 的元素加 1
int getAndSet(int i, int newValue)将位置 i 的元素以原子方式设置为给定值,并返回旧值
int incrementAndGet(int i)以原子方式将索引 i 的元素加 1
其实这些方法与AtomicInteger方法也很类似,而且从方法命名就看出是什么意思,这种通过方法、参数的名称就能够得到函数意义的写法是非常值得称赞的。
 
AtomicIntegerFieldUpdater<T>/AtomicLongFieldUpdater<T>/AtomicReferenceFieldUpdater<T,V>是基于反射的原子更新字段的值。
相应的API也是非常简单的,但是也是有一些约束的。
1.字段必须是volatile类型的!在后面的章节中会详细说明为什么必须是volatile,volatile到底是个什么东西。
2.字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。
3.只能是实例变量,不能是类变量,也就是说不能加static关键字。
4.只能是可修改变量,不能使final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。
5.对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。
public class AtomicIntegerFieldUpdaterTest {

       class DemoData {
             public volatile int value1 = 1;
             volatile int value2 = 2;
             protected volatile int value3 = 3;
             private volatile int value4 = 4;
      }

      AtomicIntegerFieldUpdater<DemoData> getUpdater(String fieldName) {
             return AtomicIntegerFieldUpdater.newUpdater(DemoData. class, fieldName);
      }

       void doit() {
            DemoData data = new DemoData();
            System. out.println("1 ==> " + getUpdater("value1" ).addAndGet(data, 10));
            System. out.println("2 ==> "
                        + getUpdater( "value2").incrementAndGet(data));
            System. out.println("3 ==> "
                        + getUpdater( "value3").decrementAndGet(data));
            System. out.println("value4 ==> "
                        + getUpdater( "value4").compareAndSet(data, 4, 5));
      }

       public static void main(String[] args) {
             //DemoData的字段value3/value4对于AtomicIntegerFieldUpdaterDemo类是不可见的,因此通过反射是不能直接修改其值的。
            AtomicIntegerFieldUpdaterTest demo = new AtomicIntegerFieldUpdaterTest();
            demo.doit();
      }

} 
执行结果为: 
1 ==> 11
2 ==> 3
Exception in thread "main" java.lang.RuntimeException: java.lang.IllegalAccessException : Class org.concurrent.atomic.AtomicIntegerFieldUpdaterTest can not access a protected member of class org.concurrent.atomic.AtomicIntegerFieldUpdaterTest$DemoData using an instance of org.concurrent.atomic.AtomicIntegerFieldUpdaterTest$DemoData
看到只改变了value1,value2的值
value3value4对于AtomicIntegerFieldUpdaterDemo类是不可见的,因此通过反射是不能直接修改其值
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值