玩转并发-Atomic包和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新增的方法是性能提升的关键。



乐观锁

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

悲观锁

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

多线程的三大特性实现

我门知道多线程三大特性:

  • 原子性
  • 有序性。
  • 可见性

volatile能实现后两者,而CAS操作是原子操作,实现了原子性


ABA问题

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

举个栗子

CAS操作忽略过程,只关注前后结果的某处是否一致。比如链表的操作,即使链表头节点不改变,但是链表不一定代表不改变,链表中的节点很有可能已经发生改变。

解决方案

解决ABA最简单的方案就是给值加一个修改版本号,每次值变化,都会修改它版本号,CAS操作时都对比此版本号,引入AtomicStampedReference。AtomicStampedReference主要维护包含一个对象引用以及一个可以自动更新的整数"stamp"的pair对象来解决ABA问题。

AtomicStampedReference

看个栗子:

public class Test {
	//定义初始内存值和时间戳
	private static final AtomicStampedReference<Integer> atomicref=new AtomicStampedReference<Integer>(100,0);
	
	public static void main(String[] args) throws InterruptedException {
		Thread thread1 = new Thread() {
			@Override
			public void run() {
				//执行ABA操作
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				 boolean success;
				 success=atomicref.compareAndSet(100,101,atomicref.getStamp(),atomicref.getStamp()+1);
				 System.out.println(success);
				 success=atomicref.compareAndSet(101,100,atomicref.getStamp(),atomicref.getStamp()+1);
				 System.out.println(success);
			}
		};
		
		Thread thread2 = new Thread() {
			@Override
			public void run() {
				boolean success;
				//这里获得的时间戳是在线程1执行之前的
				int stamp=atomicref.getStamp();
				System.out.println("Before sleep:"+stamp);
				try {
					TimeUnit.SECONDS.sleep(3);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				//这里无法成功,因为时间戳不同
				success=atomicref.compareAndSet(100,101,stamp,stamp+1);
				System.out.println(success);
			}
		};
		
		thread1.start();
		thread2.start();
		thread1.join();
		thread2.join();	
	}
}

打印结果:

Before sleep:0
true
true
false

当AtomicStampedReference设置对象值时,对象值以及时间戳都必须满足期望值,写入才会成功。因此,即使对象值被反复读写,写回原值,只要时间戳发生变化,就能防止不恰当的写入

剖析AtomicStampedReference源码
 //内部类Pair,存放需要原子操作的引用和时间戳
 private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
    
//构造函数
//通过构造函数传入原子引用和设定的时间戳
public AtomicStampedReference(V initialRef, int initialStamp) {
		//调用内部类方法,返回Pair
        pair = Pair.of(initialRef, initialStamp);
    }

 //CAS方法
 public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        //获取当前Pair
        Pair<V> current = pair;
        return
        	//只有满足期望值和内存值相同,时间戳相同才会成功执行
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

利用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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值