很多情况下我们只需要一个简单的、高效的、线程安全的递增递减方案,而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类是不可见的,因此通过反射是不能直接修改其值
相关推荐
《Java并发编程:设计原则与模式(第二版)》是一本深入探讨Java多线程编程技术的权威著作。这本书详细阐述了在Java平台中进行高效并发处理的关键概念、设计原则和实用模式。以下是对该书内容的一些核心知识点的概述...
《Java并发编程实战》是Java并发编程领域的一本经典著作,它深入浅出地介绍了如何在Java平台上进行高效的多线程编程。这本书的源码提供了丰富的示例,可以帮助读者更好地理解书中的理论知识并将其应用到实际项目中。...
本文将深入探讨Java并发编程的设计原则与模式,旨在帮助开发者理解并有效地应用这些原则和模式。 一、并发编程的基础概念 并发是指两个或多个操作在同一时间间隔内执行,而不是严格意义上的同一时刻。在Java中,...
Java并发编程是软件开发中的一个关键领域,尤其是在大型企业级应用和分布式系统中。通过学习相关的书籍,开发者可以深入理解如何有效地设计和实现高效的多线程应用程序,避免并发问题,如竞态条件、死锁、活锁等。...
在Java并发编程领域,以下几个核心知识点是本书的重点: 1. **线程基础**:介绍Java中的Thread类,如何创建和启动线程,以及线程的生命周期状态(新建、就绪、运行、阻塞和死亡)。 2. **并发控制**:讲解...
### Java并发编程实践 #### 书籍概述 《Java并发编程实践》是一本由Brian Goetz等人编写的关于Java并发编程的经典著作。本书深入浅出地介绍了Java 5.0及之后版本中新增加的并发特性,并对并发编程进行了全面而详尽...
Java并发编程是Java开发中的重要领域,特别是在大型分布式系统、多线程应用和服务器端程序设计中不可或缺。这个资源包“Java并发编程从入门到精通源码.rar”显然是为了帮助开发者深入理解并掌握这一关键技能。它包含...
Java并发编程是Java语言中最为复杂且重要的部分之一,它涉及了多线程编程、内存模型、同步机制等多个领域。为了深入理解Java并发编程,有必要了解其核心技术点和相关实现原理,以下将详细介绍文件中提及的关键知识点...
《JAVA并发编程实践》这本书是Java开发者深入理解并发编程的重要参考资料。并发编程是现代多核处理器环境下不可或缺的技术,它能够充分利用系统资源,提高程序的执行效率。以下将详细阐述Java并发编程的一些关键知识...
《Java并发编程实战》这本书是Java开发者深入理解并发编程的重要参考资料。并发编程是现代软件开发中的核心技能之一,尤其是在多核处理器和分布式系统环境中。Java平台提供了强大的并发工具和框架,使得开发者能够...
在Java并发编程中,CAS(Compare and Swap)是一种无锁算法,用于在多线程环境中实现对共享变量的原子性更新。相比于传统的锁机制,如`synchronized`和`Lock`,CAS具有更低的开销,因为它避免了线程阻塞。本文将深入...
《Java并发编程实践》一书深入探讨了Java平台在Java 5.0和Java 6中引入的并发特性,以及并发编程的一般性原理。本书不仅由参与设计和实现这些特性的团队撰写,而且得到了业界专家的高度评价,如Sun Microsystems的...
Java并发编程是Java开发中的重要领域,它涉及到多线程、同步机制、线程池以及并发集合等核心概念。在Java中,并发编程是提高系统性能和资源利用率的关键技术,尤其是在处理大量I/O操作或者计算密集型任务时。本文将...
### 并发编程之CAS与Atomic原子操作详解 #### 一、原子操作的概念与意义 在计算机科学领域,原子操作是指一系列的操作被视为一个整体,在执行过程中不会被其他进程或线程打断的操作。简而言之,它确保了一系列操作...
《Java并发编程艺术》是Java开发者深入理解并发编程的一本经典著作。这本书涵盖了Java多线程和并发库的广泛主题,旨在帮助开发者有效地利用多核处理器的优势,编写出高效、可扩展且线程安全的代码。以下是根据书名和...
《Java并发编程实战》是一本深入探讨Java多线程和并发编程的经典著作,它由Brian Goetz、Tim Peierls、Joshua Bloch、David Holmes和Doug Lea合著,旨在帮助开发者理解和掌握Java并发的核心概念和技术。这本书的源码...
在探讨Java并发编程与高并发解决方案之前,我们首先需要理解几个关键概念:并发(Concurrency)、高并发(High Concurrency)以及多线程(Multithreading)。这些概念是现代软件开发中不可或缺的一部分,尤其是在...
根据提供的标题、描述和标签,本文将围绕“Java并发编程实践”这一主题展开,深入探讨Java并发编程的基础概念、核心原理以及实际应用技巧。 ### 一、Java并发编程概述 Java并发编程是Java开发中一个非常重要的领域...
《Java并发编程实践》这本书是Java开发者深入理解并发编程的重要参考资料。并发编程是现代软件开发中的核心技能之一,尤其是在多核处理器普及后,利用并发能够极大地提升程序的执行效率。以下将根据提供的章节标题,...
Java并发编程库,特别是java.util.concurrent(简称J.U.C),是Java语言在多线程处理上的一大亮点。并发编程是一个复杂的话题,因为它涉及到许多高级概念,包括线程安全、死锁、性能优化和原子操作等。J.U.C正是为了...