概述
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可以执行以下几种操作:
- 分配内存和释放内存,在方法allocateMemory,reallocateMemory,freeMemory中,有点类似c中的malloc,free方法
- 可以定位对象的属性在内存中的的位置,可以修改对象的属性值
- 挂起和恢复线程
- 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
…