玩转并发-Atomic包下AtomicInteger和CAS原理

时间:2019-02-18
本文章向大家介绍玩转并发-Atomic包下AtomicInteger和CAS原理,主要包括玩转并发-Atomic包下AtomicInteger和CAS原理使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

概述

JDK1.5之后的java.util.concurrent.atomic包里,多了一批原子处理类。AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference。主要用于在高并发环境下的高效程序处理,来帮助我们简化同步处理.

AtomicInteger

AtomicInteger提供原子操作Integer的类。在Java语言中,i++和++i操作并不是线程安全的,在使用的时候不可避免造成数据不同步,而AtomicInteger提供线程安全的加减操作接口。
AtomicIntefer提供的API:

public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta)  //获取当前的值,并加上预期的值

看个栗子:

private static AtomicInteger count = new AtomicInteger(0);
	
	public static void incrment() {
		count.incrementAndGet();
	}
	
	public static int getCount() {
		return count.get();
	}

测试类:

public static void main(String[] args) {
		Thread writer = new Thread() {
			@Override
			public void run() {
				while(true) {
					try {
						System.out.println("writer:--"+Integer.toString(getCount()));
						incrment();
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		};
		Thread reader = new Thread() {
			@Override
			public void run() {
				while(true) {
					try {
						System.out.println("reader:--"+Integer.toString(getCount()));
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		};
		writer.start();
		reader.start();	
	}

打印结果:

writer:–0
reader:–0
reader:–1
writer:–1
reader:–2
writer:–2
reader:–3
writer:–3

我们并没有通过加锁的方式实现数据同步,AtomicInteger可以实现线程安全。AtomicInteger由硬件提供原子操作指令实现,相比显氏锁和synchronized,开销更小速度更快。


AtomicInteger源码剖析

打开源码,我们先看下AtomicInteger下三个域属性:

 // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

这里, unsafe是java提供的获得对对象内存地址访问的类,注释已经清楚的写出了,它的作用就是在更新操作时提供“比较并替换”的作用。实际上就是AtomicInteger中的一个工具。unsafe类是一个可以执行不安全、容易犯错的操作的一个特殊类。虽然Unsafe类中所有方法都是public的,但是这个类只能在一些被信任的代码中使用
unsafe可以执行以下几种操作:

  1. 分配内存和释放内存,在方法allocateMemory,reallocateMemory,freeMemory中,有点类似c中的malloc,free方法
  2. 可以定位对象的属性在内存中的的位置,可以修改对象的属性值
  3. 挂起和恢复线程
  4. CAS操作(Compare And Swap),比较并交换,是一个原子操作
    AtomicInteger中使用的就是unsafe的CAS操作方法
 /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
	
	/**
	*参数o就是要进行CAS操作的对象
	*参数offest是内存地址
	*参数expected就是期望的值
	*参数x就是需要更新到的值
	*/
	public final native boolean compareAndSwapInt(Object o, long offset,
                                                int expected,
                                                int x);

valueOffset是用来记录value本身在内存的偏移地址的,这个记录,也主要是为了在更新操作在内存中找到value的位置,方便比较。
注意:value是用来存储整数的时间变量,这里被声明为volatile,就是为了保证在更新操作时,当前线程可以拿到value最新的值(并发环境下,value可能已经被其他线程更新了)。


JDK1.8下的AtomicInteger和JDK1.7下的AtomicInteger的实现是不同的:
JDK1.7下的addAndGet方法:

public final int addAndGet(int delta) {
	//死循环
    for (;;) {
    	//得到当前值
        int current = get();
        //当前值加上delta
        int next = current + delta;
        //使用CAS原子操作确保线程安全
        if (compareAndSet(current, next))
            return next;
    }
}

JDK1.8下的addAndGet方法

 public final int addAndGet(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
    }

在jdk1.8中,直接使用了Unsafe的getAndAddInt方法,而在jdk1.7的Unsafe中,没有此方法。基本可以断定,Unsafe新增的方法是性能提升的关键。


AtomicInteger原理


乐观锁

核心思路:每次进行某项操作乐观相信没有冲突而不加锁,假如监测到冲突就失败重试,直到成功

悲观锁

java里面synchronized就是悲观锁,它采用排他机制,是一种独占锁。先假设一种最坏的情况"资源是被占用的",并且在占用其间会导致其他需要锁的线程挂起,等待持有锁的线程放锁。
悲观锁的缺点:由于在进程挂起和恢复执行过程中存在着很大的开销,假设一个线程需要某个资源,但是这个资源的占用时间很短,当线程第一次抢占这个资源时,可能这个资源被占用,如果此时挂起这个线程,可能立刻就发现资源可用,然后又需要花费很长的时间重新抢占锁,时间代价就会非常的高。所以当数据争用不严重时,乐观锁效果更好。


Java中CAS的实现

CAS就是Compare And Swap的意思,比较并操作。很多CPU支持CAS指令,CAS是项乐观锁技术。当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其他线程都失败;失败的线程并不会被挂起,而是被告知这次竞争失败,并可以再次尝试。CAS有三个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当旧的预期值A和内存值V相等,将内存值修改为B,否则什么都不做。
CAS的核心思想:先从内存V处拿出一个值作为预期值A,等到将要将内存值V修改为B时,判断A是否等于V,如果A不等于V了,说明有别的线程改变了内存值,实行快速失败机制,重新尝试以上操作,直到A==V,确保线程安全,再将V改为B

ABA问题

CAS看起来很好,但是会导致“ABA”问题。
CAS算法实现的一个重要前提是取出内存中某时刻的数据,而在下时刻进行比较并替换,那么在这个时间差会导致数据的变化。
假设线程A从内存位置V取出A,此时线程B也从内存位置V取出A,并且线程B进行一些操作,将A变成B,之后又将V位置的数据变成A。这时候线程A进行CAS操作时,发现旧的预期值和内存值相等,之后线程A操作成功。尽管CAS操作成功,但是不代表这个过程没有问题。

解决方案

利用CAS构造显氏锁TryLock

CASLock锁

public class CASLock {
	 //AtomicInteger是引用类型,final保证堆内存中地址不变,不保证栈内存中引用不改变
	 private final AtomicInteger value= new AtomicInteger(0);
	 //持有锁的线程才有权限解锁
	 private Thread lockedThread;
	 
	 public void tryLock() throws GetLockException {
		 boolean success = value.compareAndSet(0, 1);
		 if(!success) {
			 throw new GetLockException();
		 }
		 lockedThread=Thread.currentThread();
	 }
	 
	 public void unLock() {
		 if(0==value.get()) {
			 return;
		 }
		 if(lockedThread==Thread.currentThread())
		 value.compareAndSet(1,0);
	 }
}

GetLockException异常

public class GetLockException extends Exception {
	
	public GetLockException() {
		super();
	}
	
	public GetLockException(String exception) {
		super(exception);
	}
}

测试类:

public static void main(String[] args) {
		CASLock casLock = new CASLock();
		
		for(int i=0;i<5;i++) {
			new Thread() {
				@Override
				public void run() {
					while(true) {
						try {
							casLock.tryLock();
							//业务逻辑
							doSomething();
						} catch (GetLockException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}finally{
							casLock.unLock();}
					}
					}
			}.start();
		}
	}

打印结果:

at com.Reyco.MyThread.CASLock.tryLock(CASLock.java:12)
at com.Reyco.MyThread.Test$1.run(Test.java:13)
com.Reyco.MyThread.GetLockException
at com.Reyco.MyThread.CASLock.tryLock(CASLock.java:12)
at com.Reyco.MyThread.Test$1.run(Test.java:13)
com.Reyco.MyThread.GetLockException