`
yuwenlin2008
  • 浏览: 124761 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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类是不可见的,因此通过反射是不能直接修改其值
 
1
1
分享到:
评论
1 楼 xumiller 2018-04-16  
我试着 value3可以...  就是4不行

protected 不是比默认 范围 大吗?

相关推荐

    java编发编程:JUC综合讲解

    Java 并发编程在现代软件开发中占据重要地位,尤其是在多核处理器的时代。JUC(java.util.concurrent)库是 Java 标准库的一部分,提供了丰富的多线程并发工具,旨在帮助开发者编写高性能、高可伸缩性的并发程序。...

    汪文君高并发编程实战视频资源下载.txt

    │ Java并发编程.png │ ppt+源码.rar │ 高并发编程第二阶段01讲、课程大纲及主要内容介绍.wmv │ 高并发编程第二阶段02讲、介绍四种Singleton方式的优缺点在多线程情况下.wmv │ 高并发编程第二阶段03讲、...

    Java并发编程相关源码集 包括多任务线程,线程池等.rar

    Java并发编程常见知识点源码集锦,涉及到对象锁,Executors多任务线程框架,线程池等示例,列出一些源码包中包括的内容:  volatile关键字的非原子性、volatile关键字的使用、AtomicInteger原子性操作、线程安全小...

    汪文君高并发编程实战视频资源全集

    │ Java并发编程.png │ ppt+源码.rar │ 高并发编程第二阶段01讲、课程大纲及主要内容介绍.wmv │ 高并发编程第二阶段02讲、介绍四种Singleton方式的优缺点在多线程情况下.wmv │ 高并发编程第二阶段03讲、...

    java并发工具包 java.util.concurrent中文版用户指南pdf

    1. java.util.concurrent - Java 并发工具包 2. 阻塞队列 BlockingQueue 3. 数组阻塞队列 ArrayBlockingQueue 4. 延迟队列 DelayQueue 5. 链阻塞队列 LinkedBlockingQueue 6. 具有优先级的阻塞队列 ...

    AtomicIntegerExample:AtomicInteger示例

    AtomicInteger示例AtomicInteger用于原子增量计数器之类的应用程序。 简短的示例代码: public class AtomicIntegerExample { private final ExecutorService execService = Executors . newFixedThreadPool( 100 );...

    java并发工具包详解

    1. java.util.concurrent - Java 并发工具包 2. 阻塞队列 BlockingQueue 3. 数组阻塞队列 ArrayBlockingQueue 4. 延迟队列 DelayQueue 5. 链阻塞队列 LinkedBlockingQueue 6. 具有优先级的阻塞队列 ...

    java并发之AtomicInteger源码分析

    AtomicInteger是java并发包下面提供的原子类,主要操作的是int类型的整型,通过调用底层Unsafe的CAS等方法实现原子操作。下面小编和大家一起学习一下

    Java并发工具包java.util.concurrent用户指南中英文对照阅读版.pdf

    java.util.concurrent - Java 并发工具包 2. 阻塞队列 BlockingQueue 3. 数组阻塞队列 ArrayBlockingQueue 4. 延迟队列 DelayQueue 5. 链阻塞队列 LinkedBlockingQueue 6. 具有优先级的阻塞队列 ...

    Java并发工具包java.util.concurrent用户指南中英文对照阅读版

    1. java.util.concurrent - Java 并发工具包 2. 阻塞队列 BlockingQueue 3. 数组阻塞队列 ArrayBlockingQueue 4. 延迟队列 DelayQueue 5. 链阻塞队列 LinkedBlockingQueue 6. 具有优先级的阻塞队列 ...

    JUC多线程学习个人笔记

    JUC(Java Util Concurrent)是Java中用于并发编程的工具包,提供了一组接口和类,用于处理多线程和并发操作。JUC提供了一些常用的并发编程模式和工具,如线程池、并发集合、原子操作等。 JUC的主要特点包括: ...

    AtomicInteger的使用,CAS的工作原理

    AtomicInteger atomicInteger = new AtomicInteger(5); atomicInteger.compareAndSet(5, 2020) + \t current data is + atomicInteger.get()) /** * Atomically sets the value to the given updated value * if ...

    AtomicInteger并发测试

    测试java.util.concurrent.atomic.AtomicInteger的类 与直接使用int做区别

    Java中对AtomicInteger和int值在多线程下递增操作的测试

    主要介绍了Java中对AtomicInteger和int值在多线程下递增操作的测试,本文得出AtomicInteger操作 与 int操作的效率大致相差在50-80倍上下的结论,需要的朋友可以参考下

    java多线程自增效率比较及原理解析

    本文介绍了多线程环境下自增效率比较及原理解析。在多线程环境下,对于...适用于Java开发人员和多线程编程爱好者等人群,内容关键词包括多线程、自增、synchronized、AtomicInteger、LongAdder、LongAccumulator等。

    面试官:谈谈你对并发编程下原子性操作的认识

    文章目录引出问题(代码示例)问题原理说明问题解决方法1:使用锁机制方法2:原子类AtomicInteger原子类CAS机制实现线程安全概述源码分析CAS与Synchronized:乐观锁,悲观锁。 概述:所谓的原子性是指在一次操作或者...

    Java AtomicInteger类的使用方法详解

    主要介绍了Java AtomicInteger类的使用方法详解,文中有具体实例代码,具有一定参考价值,需要的朋友可以了解下。

    基于Java+MySQL设计与实现的秒杀与抢购模型架构【100013279】

    3.基于AtomicInteger的CAS机制; 4.使用Redis作为原子计数器(watch事务+decr操作),RabbitMQ作为消息队列记录用户抢购行为,MySQL做异步存储。 上述四个解决方案均使用了JMeter进行压力与性能测试(实验设置的是10...

    Java AtomicInteger类使用方法实例讲解

    主要介绍了Java AtomicInteger类使用方法实例讲解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    java并发包资源

    1. java.util.concurrent - Java 并发工具包 2. 阻塞队列 BlockingQueue 3. 数组阻塞队列 ArrayBlockingQueue 4. 延迟队列 DelayQueue 5. 链阻塞队列 LinkedBlockingQueue 6. 具有优先级的阻塞队列 ...

Global site tag (gtag.js) - Google Analytics