无锁的原理详解
CAS
CAS算法的过程是这样:它包含3个参数CAS(V,E,N)。V表示要更新的变量,E表示预期值,N表示新值。仅当V
值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么
都不做。最后,CAS返回当前V的真实值。CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成
操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程
不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS
操作即时没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
一张图表示
线程a,线程 b 同时并发机型抢夺资源,对56 进行+1 ,如果cas算法, 他们首先都会读取这个资源 56 然后保存在自己的线程内存中,然后当线程拿到数据进行修改之后,拿原始数据和当前的共享的数据进行对比,如果相同就把改变后的数据也就是+1 写入共享数据区域
cas 抱着乐观的操作,完不成就重试 ,CAS整个过程 是由一条cpu 指令执行的, 这样有原子性
从指令层面保证数据一致的
与阻塞方式相比,阻塞的线程会被系统挂起,无锁的方式线程不可能被挂起的,除非你手动, 如果一个线程被挂起他作为上下文交换需要8000次时钟周期,但是CAS重试指令需要的时钟周期很小
无锁类的使用
AtomicInteger
他比较核心的就这个【参考文章】【参考文章】
private volatile int value;
这个方法 他内部封装的value 其实所有的操作都是对他的操作
/**
* 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 如果他们相同的话 , 就返回ture ,不同就返回false,如果false 就实际值和预期值不同,
unsafe 不安全,java 相对应c cpp 比较安全因为他屏蔽了指针,unsafe 底层提供了类似指针的一些操作,unsafe.compareAndSwapInt()
这个是设置偏移量 valueoffset 就是偏移量底层都是一些c的实现,
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
这个底层调用的是cpp 代码
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
让预期值和原始值进行比较如果相同就把数据改变,不同就继续循环,这种都是死循环这种类中 调用cpp 代码
package java.util.concurrent.atomic;
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,返回新值
public final int get() //取得当前值
public final void set(int newValue) //设置当前值
public final int getAndSet(int newValue) //设置新值,并返回旧值
Unsafe
线程不安全, 主要对jdk 内部使用, 不是对外提供的,,里面都是native 方法, 描述的cpp c
内存偏移量
在c cpp 中有结构体 比如 以下结构体,如果int a 的地址位0 ,站字节位4 ,+ 4 之后这个4 就是偏移量,通过偏移量就可以拿到 b
非安全的操作,比如:
根据偏移量设置值
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);
unsafe 在java 中是被大量使用的,包括java 的一些高性能框架都会使用unsafe 这个类
package com.noneLock;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author ZZQ
* @Title: java8_demo
* @Package com.noneLock
* @date 2018/7/17 14:46
*/
public class AtomicIntegerDemo {
static AtomicInteger atomicInteger = new AtomicInteger(); //十个线程对他进行操作,然后他的值应该x10
public static class AddThread implements Runnable{
@Override
public void run() {
for (int k = 0 ; k < 1000; k ++ ){
atomicInteger.incrementAndGet();
}
}
}
public static void main(String[] args) throws Exception{
Thread[] threads = new Thread[10];
for (int i = 0 ; i <threads.length; i ++){
threads[i] = new Thread(new AddThread());
}
// 启动10 个线程
for (int i = 0 ; i<threads.length; i++){threads[i].start();}
for (int i = 0 ; i<threads.length; i++){threads[i].join();}
System.out.println(atomicInteger);
}
}
AtomicReference
封装的是一个对象的引用, 只要对对象的引用进行修改, 就可以用atomicreference 保证线程安全
public class AtomicReference<V> implements java.io.Serializable {
v 支持各种类型
get()
set(V)
compareAndSet()
getAndSet(V)
上述方法底层都用了cpp 实现cas 算法
Reference
https://blog.csdn.net/androidstar_cn/article/details/54710652
package com.noneLock;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author ZZQ
* @Title: java8_demo
* @Package com.noneLock
* @date 2018/7/17 14:55
*/
public class AtomicReferenceDemo {
// 设置初始化值
public final static AtomicReference<String> at = new AtomicReference<String>("abc") ;
public static void main(String[] args) {
for (int i = 0 ; i <10 ; i ++){
int num = i ;
new Thread(()->{
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
if(at.compareAndSet("abc","def")){
System.out.println("Thread"+Thread.currentThread().getId()+"Change Value");
}else {
System.out.println("Thread"+Thread.currentThread().getId()+ "FALED");
}
}).start();
}
}
}
AtomicStampedReference
主要接口
//比较设置 参数依次为:期望值 写入新值 期望时间戳 新时间戳
public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp)
//获得当前对象引用
public V getReference()
//获得当前时间戳
public int getStamp()
//设置当前对象引用和时间戳
public void set(V newReference, int newStamp)
主要解决的就是ABA问题,
在下面的线程执行逻辑的过程中, 上面的线程把A 从 A 改为B 然后又改变为 A 然后下面的线程刚号执行完毕,然后她比较 数据相同,然后做了修改, 这样结果一致,但是不符合我们的要球, 如果程序是要球过程的就用到了这个类
代码实现案例
package com.noneLock;
import java.lang.reflect.Modifier;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @author ZZQ
* @Title: java8_demo
* @Package com.noneLock
* @date 2018/7/17 17:59
* 有stamped这个单词一般同一时刻都是唯一的
*/
public class AtomicStampedReferenceDemo {
static AtomicStampedReference<Integer> mo = new AtomicStampedReference<Integer>(18,0) ;
public static void main(String[] args) {
// 开启三个线程
for (int i =0 ; i<3 ; i++ ){
int timeStamp = mo.getStamp();
new Thread(()->{
while (true){
while (true){
Integer m = mo.getReference();
if(m<20) {
if (mo.compareAndSet(m, m + 20, timeStamp, timeStamp + 1)) {
System.out.println("余额小于20,充值成功,余额:" + mo.getReference() + "元");
break;
}
} else {
// System.out.println("余额大域20 无需充值");
break;
}
}
}
}).start();
}
new Thread(() -> {
for (int i = 0; i < 100; i++) {
while (true) {
int stamp = mo.getStamp();
Integer m = mo.getReference();
if (m > 10) {
System.out.println("大于10元");
if (mo.compareAndSet(m,m-10,stamp,stamp+1)) {
System.out.println("成功消费10元,余额:"+mo.getReference());
break;
}
}else {
System.out.println("没有足够的金额...");
break;
}
}
}
}).start();
}
}
用户一边花钱,这边一边充钱,如果要是没有时间戳timestamp 的话, 一旦低于20 就会充钱, 但是使用这个时间戳就不一样了, 不但要判断数指,还要判断timestamp
AtomicIntegerAarray
支持无锁数组
在这些性能比较高的,线程安全的atomic 类中 很多都是通过c cpp 的代码进行实现的 java 并不能实现
/**
* Gets the current value at position {@code i}.
*
* @param i the index
* @return the current value
*/
public final E get(int i) {
return getRaw(checkedByteOffset(i));
}
@SuppressWarnings("unchecked")
private E getRaw(long offset) {
return (E) unsafe.getObjectVolatile(array, offset);
}
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
计算完偏移量,然后给getraw 方法, 然后通过unsafe 调用c cpp 代码进行实现
static {
try {
unsafe = Unsafe.getUnsafe();
arrayFieldOffset = unsafe.objectFieldOffset(AtomicReferenceArray.class.getDeclaredField("array"));
base = unsafe.arrayBaseOffset(Object[].class);
int var0 = unsafe.arrayIndexScale(Object[].class);
if ((var0 & var0 - 1) != 0) {
throw new Error("data type scale not a power of two");
} else {
shift = 31 - Integer.numberOfLeadingZeros(var0);
}
} catch (Exception var1) {
throw new Error(var1);
}
}
初始化base ,
shift = 31 - Integer.numberOfLeadingZeros(var0);
numberofleadingzeros 前导0 如果整数是4 的话他的二进制是100 在他的前面要加上 29 个0 ,
int var0 = unsafe.arrayIndexScale(Object[].class);
package com.noneLock;
import javax.print.DocFlavor;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.concurrent.atomic.AtomicReferenceArray;
/**
* @author ZZQ
* @Title: java8_demo
* @Package com.noneLock
* @date 2018/7/17 20:40
*/
public class AtomicIntegerArrayDemo {
static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[10]);
public static void main(String[] args) throws Exception{
new Thread(()->{
for (int i =0; i <10000; i++){
atomicIntegerArray.getAndIncrement(i%atomicIntegerArray.length()); //每次递增
}
}).start();
Thread[] ts = new Thread[10];
for (int i = 0 ; i <10 ; i++){
ts[i] = new Thread(new AtomicIntegerDemo.AddThread());
}
for (int k = 0 ; k<10; k++){ts[k].start();}
for(int k = 0 ; k < 10 ; k ++){ts[k].join();}
System.out.println(atomicIntegerArray);
}
}
每个元素多宽? int 就是 4 个byte ,这个使用的是位运算, 因为位运算比传统的运算块,jdk 中好多为了提高效率都是使用的位运算,
private static long byteOffset(int i) {
return ((long) i << shift) + base;
}
API
//获得数组第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,设置成功返回
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
让普通变量也享受原子操
Api
AtomicIntegerFieldUpdater.newUpdater()
incrementAndGet()
package com.noneLock;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
/**
* @author ZZQ
* @Title: java8_demo
* @Package com.noneLock
* @date 2018/7/17 20:54
* 让普通的变量享受到原子更新的好处
*
*/
public class AtomicLongFieldUpdaterDemo {
// 创建一个内粗类
static class Candidate{
int id ;
volatile long score ;
}
static AtomicLongFieldUpdater<Candidate> atomicLongFieldUpdater =
AtomicLongFieldUpdater.newUpdater(Candidate.class,"score");
//内部采用的反射技术
// 检查update 是否正确
public static AtomicInteger allsore = new AtomicInteger(0);
public static void main(String[] args) throws Exception{
Candidate candidate = new Candidate();
Thread[] threads = new Thread[10000];
for (int i = 0 ; i < 10000 ; i ++ ){
threads[i]= new Thread(()->{
if(Math.random()>0.4){
atomicLongFieldUpdater.incrementAndGet(candidate);
allsore.incrementAndGet();
}
});
threads[i].start();
}
for (int i = 0 ; i < 10000 ; i++ ){
threads[i].join();
}
System.out.println(allsore);
System.out.println(candidate.score);
}
}
注意
1.
Updater只能修改它可见范围内的变量。因为Updater使用反射得到这个变量。如果变量不可见,就会出错。
比如如果score申明为private,就是不可行的。
2.
为了确保变量被正确的读取,它必须是volatile类型的。如果我们原有代码中未申明这个类型,那么简单得
申明一下就行,这不会引起什么问题。
3. 由于CAS操作会通过对象实例中的偏移量直接进行赋值,因此,它不支持static字段(Unsafe.
objectFieldOffset()不支持静态变量)。
无锁算法详解
无锁的Vector实现
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
get方法, 获取索引上的值 , 是一个线程安全的,但是arraylist 就不一样了,他的get 是一个线程不安全的
protected Object[] elementData;
内部其实就是维护了一个object 数组,
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
他的长度增加是通过复制进行增加数组的长度进行扩容,但是这样有一个缺陷就是他到后来会浪费大量的 容器空间
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
在添加元素的时候他会先进行判断, 如果容器容量不够就grow 方法增加容量
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
LockFreeVector