JAVA并发Day4 无锁

无锁的原理详解

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

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值