博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
原子变量与synchronized详细解释
阅读量:2386 次
发布时间:2019-05-10

本文共 3676 字,大约阅读时间需要 12 分钟。

AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。

    要使用多处理器系统的功能,通常需要使用多线程构造应用程序。但是正如任何编写并发应用程序的人可以告诉你的那样,要获得好的硬件利用率,只是简单地在多个线程中分割工作是不够的,还必须确保线程确实大部分时间都在工作,而不是在等待更多的工作,或等待锁定共享数据结构。而synchronized来控制并发就需要去等待这个锁资源,这步是非常消耗资源的,处理的吞吐量也就下去了。而java的concurrent 并发包提供的AtomicInteger就提供CAS原理避免了锁等待,具体的实现是通过UnSafe类来直接操作底层的硬件资源。 

cpu 硬件同步原语(compare and swap) 

支持并发的第一个处理器提供原子的测试并设置操作,通常在单位上运行这项操作。现在的处理器(包括 Intel 和 Sparc 处理器)使用的最通用的方法是实现名为 比较并转换或 CAS 的原语。(在 Intel 处理器中,比较并交换通过指令的 cmpxchg 系列实现。PowerPC 处理器有一对名为“加载并保留”和“条件存储”的指令,它们实现相同的目地;MIPS 与 PowerPC 处理器相似,除了第一个指令称为“加载链接”。) 
  CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。” 
  通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。 
  类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法可以对该操作重新计算。CAS 操作的行为(而不是性能特征),但是 CAS 的价值是它可以在硬件中实现,并且是极轻量级的(在大多数处理器中)。

普通的类:

public class SynchronizedCounter {    private int value;    public synchronized int getValue(){        return value;    }    public synchronized int getNextValue(){        return value++;    }    public synchronized int getPreviousValue(){        return value--;    }}
增加synchronized关键字的类:

public class SynchronizedCounter {    private int value;    public synchronized int getValue(){        return value;    }    public synchronized int getNextValue(){        return value++;    }    public synchronized int getPreviousValue(){        return value--;    }}
使用原子变量的类:

import java.util.concurrent.atomic.AtomicInteger;public class AtomicCounter {    private final AtomicInteger value = new AtomicInteger(0);    public int getValue(){        return value.get();    }    public int getNextValue(){        return value.incrementAndGet();    }    public int getPreviousValue(){        return value.decrementAndGet();    }}
测试类:

public class Test {	static SynchronizedCounter counteSynchronizedCounter  = new SynchronizedCounter();	static AtomicCounter atomicCounter = new AtomicCounter();		public static long startTime = 0;	public static long endTime = 0;		public static void main(String[] args) {		startTime = System.currentTimeMillis();		for (int i = 0; i < 1 ; i++) {			Thread t = new Thread(runnable);			t.start();		}	}		static Runnable runnable = new Runnable() {		@Override		public void run() {			for(int i=0;i<100000000;i++){				counteSynchronizedCounter.getNextValue();			}			endTime = System.currentTimeMillis();			System.out.println(endTime-startTime);		}	};//开三个线程用时56936,开一个线程4368//	static Runnable runnable = new Runnable() {//		@Override//		public void run() {//			for(int i=0;i<100000000;i++){//				atomicCounter.getNextValue();//			}//			endTime = System.currentTimeMillis();//			System.out.println(endTime-startTime);//		}//	};//开三个线程用时13323,一个线程用时2288	}
上面的结果说明了原子变量比synchronized关键字的运行效率高多了。

这里解释一下, “类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法可以对该操作重新计算。”

 CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。然后对该操作重新计算,这里解释一下AtomicInteger是怎么重新计算的

AtomicInteger中的源码:

这里使用了一个无限循环,只要没有正确返回数据就一直循环。

public final int incrementAndGet() {        for (;;) {            int current = get();            int next = current + 1;            if (compareAndSet(current, next))                return next;        }    }public final boolean compareAndSet(int expect, int update) {	return unsafe.compareAndSwapInt(this, valueOffset, expect, update);    }

转载于:https://my.oschina.net/winHerson/blog/139019

你可能感兴趣的文章
Eclipse的搜索技巧
查看>>
centos常用命令二
查看>>
通过修改kong属性解决不能获取外网域名的问题
查看>>
Eclipse带命令行参数调试
查看>>
php smtp发送邮件
查看>>
yii框架的404、500等异常处理
查看>>
yii框架在layout模式下,模版和layout文件的渲染顺序
查看>>
php5对象复制、clone、浅复制与深复制
查看>>
php设计模式
查看>>
git与github在ubuntu下的使用
查看>>
css pie.htc使用总结
查看>>
python包含中文字符串长度
查看>>
sysbench 0.5 性能测试工具使用手册
查看>>
通过telnet连接查看memcache服务器
查看>>
django不用在数据库中创建新的user表而使用它的后台管理功能
查看>>
php array_unshift()修改数组key
查看>>
mysql性能优化-查询(Query)优化-2
查看>>
MySQL分区表的使用
查看>>
MongoDB 地理位置索引的实现原理
查看>>
MongoDB与MySQL的插入、查询性能测试
查看>>