原子变量与synchronized详细解释

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

    要使用多处理器系统的功能,一般须要使用多线程构造应用程序。可是正如任何编写并发应用程序的人能够告诉你的那样,要得到好的硬件利用率,只是简单地在多个线程中分割工做是不够的,还必须确保线程确实大部分时间都在工做,而不是在等待更多的工做,或等待锁定共享数据结构。而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中的源码: ide

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

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);
    }
相关文章
相关标签/搜索