AtomicInteger在实际项目中的应用
2014-04-10 18:17
274 查看
AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。
来看AtomicInteger提供的接口。
//获取当前的值
public final int get()
//取当前的值,并设置新的值
public final int getAndSet(int newValue)
//获取当前的值,并自增
public final int getAndIncrement()
//获取当前的值,并自减
public final int getAndDecrement()
//获取当前的值,并加上预期的值
public final int getAndAdd(int delta)
... ...
我们在上一节提到的CAS主要是这两个方法
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
这两个方法是名称不同,但是做的事是一样的,可能在后续的java版本里面会显示出区别来。
详细查看会发现,这两个接口都是调用一个unsafe的类来操作,这个是通过JNI实现的本地方法,细节就不考虑了。
下面是一个对比测试,我们写一个synchronized的方法和一个AtomicInteger的方法来进行测试,直观的感受下性能上的差异
package zl.study.concurrency;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerCompareTest {
private int value;
public AtomicIntegerCompareTest(int value){
this.value = value;
}
public synchronized int increase(){
return value++;
}
public static void main(String args[]){
long start = System.currentTimeMillis();
AtomicIntegerCompareTest test = new AtomicIntegerCompareTest(0);
for( int i=0;i< 1000000;i++){
test.increase();
}
long end = System.currentTimeMillis();
System.out.println("time elapse:"+(end -start));
long start1 = System.currentTimeMillis();
AtomicInteger atomic = new AtomicInteger(0);
for( int i=0;i< 1000000;i++){
atomic.incrementAndGet();
}
long end1 = System.currentTimeMillis();
System.out.println("time elapse:"+(end1 -start1) );
}
}
结果
time elapse:31
time elapse:16
由此不难看出,通过JNI本地的CAS性能远超synchronized关键字
下面再贴一个简单的例子:
Java代码
CashierContext类部分代码:
private Map<String, AtomicInteger> counter = new HashMap<String, AtomicInteger>();
private void initCounter() {
counter.put("cvm", new AtomicInteger(0));
}
//被调用一次自动+1
public MobileCashierViewModel getCvm() {
if (cvm != null) {
counter.get("cvm").incrementAndGet();
}
return cvm;
}
使用场景:
因为通过WS服务获取MobileCashierViewModel 这个对象比较频繁,会很影响系统资源,可以将cvm存入缓存中,想要查看缓存cvm有多大价值,那么可以设置一个计数,来统记cvm被调用的次数
然后将CashierContext放入ThreadLocal中,然后再写一个过滤器,在过滤器里面可以得到获取这个服务从缓存中取的次数,这个就可以很容易看出来缓存价值。
那么为什么不使用记数器自加呢,例如count++这样的,因为这种计数是线程不安全的,高并发访问时统计会有误,而AtomicInteger为什么能够达到多而不乱,处理高并发应付自如呢,我们才看看AtomicInteger的源代码:
Java代码
private volatile int value;
大家可以看到有这个变量,value就是你设置的自加起始值。注意看它的访问控制符,是volatile,这个就是保证AtomicInteger线程安全的根源,熟悉并发的同学一定知道在java中处理并发主要有两种方式:
1,synchronized关键字,这个大家应当都各种面试和笔试中经常遇到。
2,volatile修饰符的使用,相信这个修饰符大家平时在项目中使用的也不是很多。
这里重点说一下volatile:
Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存重新读取该成员的值,而且,当成员变量值发生变化时,强迫将变化的值重新写入共享内存,这样两个不同的线程在访问同一个共享变量的值时,始终看到的是同一个值。
java语言规范指出:为了获取最佳的运行速度,允许线程保留共享变量的副本,当这个线程进入或者离开同步代码块时,才与共享成员变量进行比对,如果有变化再更新共享成员变量。这样当多个线程同时访问一个共享变量时,可能会存在值不同步的现象。
而volatile这个值的作用就是告诉VM:对于这个成员变量不能保存它的副本,要直接与共享成员变量交互。
建议:当多个线程同时访问一个共享变量时,可以使用volatile,而当访问的变量已在synchronized代码块中时,不必使用。
缺点:使用volatile将使得VM优化失去作用,导致效率较低,所以要在必要的时候使用。
来看AtomicInteger提供的接口。
//获取当前的值
public final int get()
//取当前的值,并设置新的值
public final int getAndSet(int newValue)
//获取当前的值,并自增
public final int getAndIncrement()
//获取当前的值,并自减
public final int getAndDecrement()
//获取当前的值,并加上预期的值
public final int getAndAdd(int delta)
... ...
我们在上一节提到的CAS主要是这两个方法
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
这两个方法是名称不同,但是做的事是一样的,可能在后续的java版本里面会显示出区别来。
详细查看会发现,这两个接口都是调用一个unsafe的类来操作,这个是通过JNI实现的本地方法,细节就不考虑了。
下面是一个对比测试,我们写一个synchronized的方法和一个AtomicInteger的方法来进行测试,直观的感受下性能上的差异
package zl.study.concurrency;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerCompareTest {
private int value;
public AtomicIntegerCompareTest(int value){
this.value = value;
}
public synchronized int increase(){
return value++;
}
public static void main(String args[]){
long start = System.currentTimeMillis();
AtomicIntegerCompareTest test = new AtomicIntegerCompareTest(0);
for( int i=0;i< 1000000;i++){
test.increase();
}
long end = System.currentTimeMillis();
System.out.println("time elapse:"+(end -start));
long start1 = System.currentTimeMillis();
AtomicInteger atomic = new AtomicInteger(0);
for( int i=0;i< 1000000;i++){
atomic.incrementAndGet();
}
long end1 = System.currentTimeMillis();
System.out.println("time elapse:"+(end1 -start1) );
}
}
结果
time elapse:31
time elapse:16
由此不难看出,通过JNI本地的CAS性能远超synchronized关键字
下面再贴一个简单的例子:
Java代码
CashierContext类部分代码:
private Map<String, AtomicInteger> counter = new HashMap<String, AtomicInteger>();
private void initCounter() {
counter.put("cvm", new AtomicInteger(0));
}
//被调用一次自动+1
public MobileCashierViewModel getCvm() {
if (cvm != null) {
counter.get("cvm").incrementAndGet();
}
return cvm;
}
使用场景:
因为通过WS服务获取MobileCashierViewModel 这个对象比较频繁,会很影响系统资源,可以将cvm存入缓存中,想要查看缓存cvm有多大价值,那么可以设置一个计数,来统记cvm被调用的次数
然后将CashierContext放入ThreadLocal中,然后再写一个过滤器,在过滤器里面可以得到获取这个服务从缓存中取的次数,这个就可以很容易看出来缓存价值。
那么为什么不使用记数器自加呢,例如count++这样的,因为这种计数是线程不安全的,高并发访问时统计会有误,而AtomicInteger为什么能够达到多而不乱,处理高并发应付自如呢,我们才看看AtomicInteger的源代码:
Java代码
private volatile int value;
大家可以看到有这个变量,value就是你设置的自加起始值。注意看它的访问控制符,是volatile,这个就是保证AtomicInteger线程安全的根源,熟悉并发的同学一定知道在java中处理并发主要有两种方式:
1,synchronized关键字,这个大家应当都各种面试和笔试中经常遇到。
2,volatile修饰符的使用,相信这个修饰符大家平时在项目中使用的也不是很多。
这里重点说一下volatile:
Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存重新读取该成员的值,而且,当成员变量值发生变化时,强迫将变化的值重新写入共享内存,这样两个不同的线程在访问同一个共享变量的值时,始终看到的是同一个值。
java语言规范指出:为了获取最佳的运行速度,允许线程保留共享变量的副本,当这个线程进入或者离开同步代码块时,才与共享成员变量进行比对,如果有变化再更新共享成员变量。这样当多个线程同时访问一个共享变量时,可能会存在值不同步的现象。
而volatile这个值的作用就是告诉VM:对于这个成员变量不能保存它的副本,要直接与共享成员变量交互。
建议:当多个线程同时访问一个共享变量时,可以使用volatile,而当访问的变量已在synchronized代码块中时,不必使用。
缺点:使用volatile将使得VM优化失去作用,导致效率较低,所以要在必要的时候使用。
相关文章推荐
- 接口、抽象类、抽象方法和虚方法的区别
- 深复制体验(一)
- Android ViewSwitcher组件
- 数据结构和算法的基本概念
- php与xpath使用操作文本节点
- AndroidMainfest.xml文件解释
- baidu手机浏览器:帮你恶补《美国队长2》编年史
- This Android SDK requires Android Developer Toolkit version 20.0.0 or above
- MariaDB二之数据类型、字符大小写、变量
- 暴雪的哈希算法
- 实现Linux下带进度显示的cp命令 - xcp
- 坐标和长宽四舍五入对UI组件(Scroll)的影响: 关键词:Twips(缇)
- Android源码分析-点击事件派发机制
- Xperia Z2推迟发布(转)
- Python中的__init__,__call__
- 三部委通知放开非公立医疗机构服务价格
- 配置GitLab 用smtp发邮件 (gitlab-6.7.3)
- java动态代理(JDK和cglib)
- magento send email
- js判断时间是否过期