无锁类原理详解
概念:
线程的无障碍的运行。
允许线程自由进入临界区,出现数据竞争时,保证一个胜出。
相对无障碍来讲比较切实可行的方案。
原理:
compare and swop算法
CAS算法的过程是这样:它包含3个参数CAS(V,E,N)。V表示要更新的 变量,E表示预期值,N表示新值。仅当V 值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么 都不做。最后,CAS返回当前V的真实值。CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成 操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程 不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS 操作即时没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
(概括:每个线程进来竞争数据的时候都给个期望值,如果期望值跟数据实际值是相等的,那么数据没被其它线程修改过,说明数据是安全的,那么这个线程就竞争成功,允许进行数据操作, 如果跟期望值不相等那么就会判定为竞争失败,且不会被挂起允许继续尝试,也允许线程放弃。)
(参考)CPU指令:
cmpxchg
/*
accumulator = AL, AX, or EAX, depending on whether
a byte, word, or doubleword comparison is being performed
*/
if(accumulator == Destination) {
ZF = 1;
Destination = Source;
}
else {
ZF = 0;
accumulator = Destination;
}
/*逻辑解析:
如果 寄存器值==目标值
则:
设置跳转标记, 原始数据设置为目标值
否则:
不设置跳转标记 , 寄存器值 = 目标值(下次竞争数据)
*/复制代码
无锁相对有锁
有锁的情况下会导致线程被挂起,进入临界区之前也会被系统挂起,一旦线程被挂起就会导致性能下降(一旦被挂起再次做上下文交换会浪费掉80000个系统周期)。
无锁不会挂起,只会让线程做重试操作一次,也就消耗了几条指令而已(一个指令=一个系统周期)
无锁的操作方式比阻塞性能上好很多。
无锁类的使用
AtomicInteger(无锁整形)
继承了number类, 有序列化接口
底层也是使用CAS算法(比较交换实例)完成的。
所有的 操作都是针对这个value属性,AtomicInteger是他的包装而已。
主要接口
public final int get() //取得当前值
public final void set(int newValue) //设置当前值
public final int getAndSet(int newValue) //设置新值,并返回旧值
public final boolean compareAndSet(int expect, int u) //如果当前值为expect,则设置u
public final int getAndIncrement() //当前值加1,返回旧值
public final int getAndDecrement() //当前值减1,返回旧值
public final int getAndAdd(int delta) //当前值增加delta,返回旧值
public final int incrementAndGet() //当前值加1,返回新值
public final int decrementAndGet() //当前值减1,返回新值
public final int addAndGet(int delta) //当前值增加delta,返回新值
核心方法compareAndSet源码
/**
* 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);
}
复制代码
==
预期值,则以原子方式将该值设置为给定的更新值。
expect
- 预期值
update
- 新值
unsafe操作 不安全的, 因为用了类似于C的指针指针样的东西。
getAndIncrement方法:
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}复制代码
Unsafe
.概述
非安全的操作,
比如:
根据偏移量设置值
park()
底层的CAS操作
非公开API,在不同版本的JDK中, 可能有较大差异
.主要接口
//获得给定对象偏移量上的int值
public native int getInt(Object o, long offset);
//设置给定对象偏移量上的int值
public native void putInt(Object o, long offset, int x);
//获得字段在对象中的偏移量
public native long objectFieldOffset(Field f);
//设置给定对象的int值,使用volatile语义
public native void putIntVolatile(Object o, long offset, int x);
//获得给定对象对象的int值,使用volatile语义
public native int getIntVolatile(Object o, long offset);
//和putIntVolatile()一样,但是它要求被操作字段就是volatile类型的
public native void putOrderedInt(Object o, long offset, int x);
AtomicReference类
概述
对引用进行修改 是一个模板类,抽象化了数据类型
主要接口
get()
set(V)
compareAndSet()
getAndSet(V)
AtomicIntege类r使用实例:
package com.imooc.multithreading;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerDemo {
static AtomicInteger atomicInteger = new AtomicInteger();
public static class Addthread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; i++){
atomicInteger.incrementAndGet();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] thread = new Thread[10];
for (int i=0;i<10;i++){
thread[i]=new Thread(new Addthread());
}
for (int i=0;i<10;i++){
thread[i].start();
}
for (int i=0;i<10;i++){
thread[i].join();
}
System.out.println(atomicInteger);
}
}复制代码
运行结果:
预期结果无误,说明是线程安全的
AtomicReference类使用实例:
package com.imooc.multithreading;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceDemo {
public final static AtomicReference atomicReference = new AtomicReference<>("abc");
public static void main(String[] args) {
for (int i=0;i<10;i++){
final int num = i;
new Thread(){
@Override
public void run() {
try {
Thread.sleep(Math.abs((int)(Math.random()*100)));
} catch (InterruptedException e) {
e.printStackTrace();
}
if(atomicReference.compareAndSet("abc","def")){
System.out.println("线程"+Thread.currentThread().getId()+"竞争成功并修改了值");
} else{
System.out.println("线程"+Thread.currentThread().getId()+"竞争失败");
}
}
}.start();
}
}
}复制代码
运行结果:
只有一个线程胜出,其余的因为,结果被修改预期值对不上,所以竞争失败,达到预期结果,所以AtomicReference类也是线程安全的。
AtomicStampedReference(原子标记参考)
概述
ABA问题
给操作对象加上标记, 记录这个对象的历史操作, 保证业务逻辑的唯一性
主要接口
//比较设置 参数依次为:期望值 写入新值 期望时间戳 新时间戳
public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp)
//获得当前对象引用
public V getReference()
//获得当前时间戳
public int getStamp()
//设置当前对象引用和时间戳
public void set(V newReference, int newStamp)
AtomicIntegerArray
概述
支持无锁的数组
主要接口
//获得数组第i个下标的元素
public final int get(int i)
//获得数组的长度
public final int length()
//将数组第i个下标设置为newValue,并返回旧的值
public final int getAndSet(int i, int newValue)
//进行CAS操作,如果第i个下标的元素等于expect,则设置为update,设置成功返回true
public final boolean compareAndSet(int i, int expect, int update)
//将第i个下标的元素加1
public final int getAndIncrement(int i)
//将第i个下标的元素减1
public final int getAndDecrement(int i)
//将第i个下标的元素增加delta(delta可以是负数)
public final int getAndAdd(int i, int delta)
使用示例
跟预期结果一致, 线程安全。
AtomicIntegerFieldUpdater(整数字段更新)
概述 让普通变量也享受原子操作
主要接口
//工厂方法,构建需要用到变量的实例
AtomicIntegerFieldUpdater.newUpdater(Class,“变量名”)
incrementAndGet()
小说明
1. Updater只能修改它可见范围内的变量。因为Updater使用反射得到这个变量。如果变量不可见,就会出错。 比如如果score申明为private,就是不可行的。
2. 为了确保变量被正确的读取,它必须是volatile类型的。如果我们原有代码中未申明这个类型,那么简单得 申明一下就行,这不会引起什么问题。
3. 由于CAS操作会通过对象实例中的偏移量直接进行赋值,因此,它不支持static字段(Unsafe. objectFieldOffset()不支持静态变量)。
示例:
与AtomicInteger类结果一指,说明数据操作功能没问题且数据安全。