无锁CAS与Unsafe类及其并发包Atomic

在前面的文章中,我们也详谈过有锁并发的代表之一synchronized关键字。通过该关键字可以控制并发执行过程中有且只有一个线程可以操作共享资源。其原理是通过当前线程持有当前对象锁,从而拥有访问权限,而其他没有持有当前对象锁的线程没有访问权限,也就保证了线程安全,但是在本篇文章中,我们将会了解到另外一种反向而行的策略,即无锁并发,即不加锁也能让保证并发执行的安全性。

本篇的思路是先阐明无锁执行者CAS的核心算法原理然后分析Java执行CAS的实践者Unsafe类,该类中的方法都是native修饰的,因此我们会以说明方法作用为主介绍Unsafe类,最后再介绍并发包中的Atomic系统使用CAS原理实现的并发类,以下是主要内容

1. 无锁的概念

在谈论无锁概念时,总会关联起乐观锁和悲观锁,悲观锁是将资源锁住,等之前获得锁的线程释放锁之后,其他线程才能访问。而乐观锁采取一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加上version来获取数据,性能较悲观锁有了很大的提高。这两种派系映射到并发编程中就如同加锁和无锁的策略。即加锁是一种悲观策略,无锁是一种乐观策略。因为对于加锁的并发程序来说,它们总是认为每次访问共享资源时总会发生冲突,因此必须对每一次数据操作实施加锁策略。而无锁则总是假设对共享资源的访问没有冲突,线程可以不停执行,无需加锁,无需等待,一旦发现冲突,无锁策略则采用一种称为CAS的技术来保证线程执行的安全性,这项CAS技术就是无锁策略实现的关键,下面我们进一步了解CAS技术的奇妙之处。

2. 什么是CAS?

CAS的全称是Compare and Swap,即比较并交换。

CAS 操作包含三个操作数 :

  • 内存位置(V)。
  • 预期原值(A)。
  • 新值(B)

当更新一个变量的时候,只有当变量的预期原值A和内存位置V当中的实际值相同时,才会将内存地址V对应的值修改为B。如果不相等,说明有其他线程修改了值,此时不执行更新操作,但可以选择重新读取该变量再尝试再次修改该变量,也可以放弃操作。

我们看个例子:

  1. 在内存地址V当中,存在着值为100的变量。
    在这里插入图片描述
  2. 此时线程1想把变量的值变为50,对于线程1来说,旧的预期值A=100,要修改的新值B=50。
    在这里插入图片描述
  3. 在线程1要提交更新之前,另一个线程2抢先一步,把内存地址中V的变量值更新成200。
    在这里插入图片描述
  4. 线程1开始提交更新,首先进行内存地址V的实际值跟A比较,发现A不等于V的实际值,提交失败。
    blog.csdnimg.cn/e7d8592de4e740f3ad0ecfbd1eb1e501.png)
  5. 线程1重新获取内存地址V的当前值,并重新计算想要修改的值,此时对于线程1来说,A=200,B=50。这个重新尝试的过程被称为自旋。
    在这里插入图片描述
  6. 这一次比较幸运,没有其他线程改变地址V的值。线程1进行比较,发现A和地址V的实际值是相等的。
    在这里插入图片描述
  7. 线程1进行交换,把地址V的值替换为B,也就是50.
    在这里插入图片描述
    由于CAS操作属于乐观锁,它总认为自己可以完成操作,当多个线程同时使用CAS操作操作一个变量时,只有一个会胜出,并成功更新。其余线程均会失败,但是失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS的操作即使没有锁,同样知道其他线程对共享操作资源的影响,并执行相应的处理措施,同时从这点也可以看出,由于无锁操作中没有锁的存在,因此不可能出现死锁的情况,也就是说无锁操作天生免疫死锁。

3. CPU指令对CAS的支持

可能我们会有这样一个疑问,假设存在多个线程执行CAS操作并且CAS步骤很多,有没有可能在判断V和A相同后,正要赋值时,切换了线程,更改了值,**造成数据不一致呢?**答案是否定的,因为CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用与完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被打断。也就是说CAS是一个CPU原子指令,不会造成数据不一致的问题。

4. Unsafe类

Unsafe类存在与sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,单从名称看来就可以知道该类是非安全的,毕竟Unsafe拥有着和C类似的指针操作,因此总是不应该首先使用Unsafe类,Java官方也不建议直接使用的Unsafe类。但是我们还是有必要了解该类。因为Java中的CAS操作的执行依赖于Unsafe类的方法,注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都是直接调用操作系统底层资源执行相应的认为,关于Unsafe类的主要功能点如下。

4.1 内存管理

Unsafe类中存在直接操作内存的方法

//分配内存指定大小的内存
public native long allocateMemory(long bytes);
//根据给定的内存地址address设置重新分配指定大小的内存
public native long reallocateMemory(long address, long bytes);
//用于释放allocateMemory和reallocateMemory申请的内存
public native void freeMemory(long address);
//将指定对象的给定offset偏移量内存块中的所有字节设置为固定值
public native void setMemory(Object o, long offset, long bytes, byte value);
//设置给定内存地址的值
public native void putAddress(long address, long x);
//获取指定内存地址的值
public native long getAddress(long address);

//设置给定内存地址的long值
public native void putLong(long address, long x);
//获取指定内存地址的long值
public native long getLong(long address);
//设置或获取指定内存的byte值
public native byte  getByte(long address);
public native void  putByte(long address, byte x);
//其他基本数据类型(long,char,float,double,short等)的操作与putByte及getByte相同

//操作系统的内存页大小
public native int pageSize();

4.2 提供实例对象新途径

//传入一个对象的class并创建该实例对象,但不会调用构造方法
public native Object allocateInstance(Class cls) throws InstantiationException;

4.3 类和实例对象以及变量的操作

//获取字段f在实例对象中的偏移量
public native long objectFieldOffset(Field f);
//静态属性的偏移量,用于在对应的Class对象中读写静态属性
public native long staticFieldOffset(Field f);
//返回值就是f.getDeclaringClass()
public native Object staticFieldBase(Field f);

//获得给定对象偏移量上的int值,所谓的偏移量可以简单理解为指针指向该变量的内存地址,
//通过偏移量便可得到该对象的变量,进行各种操作
public native int getInt(Object o, long offset);
//设置给定对象上偏移量的int值
public native void putInt(Object o, long offset, int x);

//获得给定对象偏移量上的引用类型的值
public native Object getObject(Object o, long offset);
//设置给定对象偏移量上的引用类型的值
public native void putObject(Object o, long offset, Object x);
//其他基本数据类型(long,char,byte,float,double)的操作与getInthe及putInt相同

//设置给定对象的int值,使用volatile语义,即设置后立马更新到内存对其他线程可见
public native void  putIntVolatile(Object o, long offset, int x);
//获得给定对象的指定偏移量offset的int值,使用volatile语义,总能获取到最新的int值。
public native int getIntVolatile(Object o, long offset);

//其他基本数据类型(long,char,byte,float,double)的操作与putIntVolatile及getIntVolatile相同,引用类型putObjectVolatile也一样。


//与putIntVolatile一样,但要求被操作字段必须有volatile修饰
public native void putOrderedInt(Object o,long offset,int x);

下面通过一个简单的Demo来演示上述的一些方法以便加深对Unsafe类的理解

/**
 * 验证Unsafe类中的方法
 */
public class UnsafeDemo {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        //通过反射得到theUnsafe对应的Filed对象
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        //设置该Filed为可访问的
        field.setAccessible(true);
        //通过该Filed得到该Filed对应的具体对象,传入null是因为该Field是static
        Unsafe unsafe = (Unsafe) field.get(null);
        System.out.println(unsafe);
        //输出结果: sun.misc.Unsafe@4769b07b


        //通过allocateInstance直接创建对象,不调用构造方法
        User user = (User) unsafe.allocateInstance(User.class);
        System.out.println("User"+user);
        //输出结果:UserUser{name='null', age=0', id=USER_ID'}

        Class userClass = user.getClass();

        Field name = userClass.getDeclaredField("name");
        Field age = userClass.getDeclaredField("age");
        Field id = userClass.getDeclaredField("id");

        //获取实例变量name和age在对象内存中的偏移量并设置值
        unsafe.putInt(user,unsafe.objectFieldOffset(age),22);
        unsafe.putObject(unsafe,unsafe.objectFieldOffset(name),"张三");

        //这里返回User.class
        Object staticBase = unsafe.staticFieldBase(id);
        System.out.println("staticBase:"+staticBase);
        //输出结果:staticBase:class com.example.test.User

        //获取静态变量id的偏移量staticOffset
        long staticOffset  = unsafe.staticFieldOffset(id);
        //获取静态变量的值
        System.out.println("设置前的ID: "+unsafe.getObject(staticBase,staticOffset));
        //输出结果:设置前的ID: USER_ID
        //设置值
        unsafe.putObject(staticBase,staticOffset,"66666666");
        //获取静态变量的值
        System.out.println("设置后的ID:"+unsafe.getObject(staticBase,staticOffset));
        //输出结果:设置后的ID: 66666666
        //输出USER
        System.out.println("输出USER:"+user.toString());
        //输出结果:输出USER:User{name='null', age=22', id=66666666'}

        long data = 1000;
        byte size = 1;//单位字节

        //调用allocateMemory分配内存,并获取内存地址memoryAddress
        long memoryAddress = unsafe.allocateMemory(size);
        直接往内存写入数据
        unsafe.putAddress(memoryAddress,data);
        //获取指定内存地址的数据
        long addrData=unsafe.getAddress(memoryAddress);
        System.out.println("addrData:"+addrData);
        //输出结果:addrData:1000
    }
}

		/**
		sun.misc.Unsafe@4769b07b
		UserUser{name='null', age=0', id=USER_ID'}
		staticBase:class com.example.test.User
		设置前的ID: USER_ID
		设置后的ID:66666666
		输出USER:User{name='null', age=22', id=66666666'}
		addrData:1000
		*/
class User{
    public User(){
        System.out.println("user 构造方法被调用");
    }
    private String name;
    private int age;
    private static String id="USER_ID";


    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +'\'' +
                ", id=" + id +'\'' +
                '}';
    }
}

虽然在Unsafe类中存在getUnsafe()方法,但该方法只提供给高级的Bootstrap类加载器使用,普通用户调用将抛出异常,所以我们在Demo中使用了反射技术获取了Unsafe实例对象并进行相关操作。

public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

4.4 数组操作

//获取数组第一个元素的偏移地址
public native int arrayBaseOffset(Class arrayClass);
//数组中一个元素占据的内存空间,arrayBaseOffset与arrayIndexScale配合使用,可定位数组中每个元素在内存中的位置
public native int arrayIndexScale(Class arrayClass);

4.5 CAS 操作相关(重点)

CAS是一些CPU直接支持的指令,也就是我们前面分析的无锁操作,在Java中无锁操作CAS基于以下三个方法实现,在稍后讲解Atomic系列内部方法是基于下述方法的实现的。

//第一个参数o为给定对象,offset为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值,
//expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作。
public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);                                                                                                  

public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);

public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);

这里还需介绍Unsafe类中JDK 1.8新增的几个方法,它们的实现是基于上述的CAS方法,如下


 //1.8新增,给定对象o,根据获取内存偏移量指向的字段,将其增加delta,
 //这是一个CAS操作过程,直到设置成功方能退出循环,返回旧值
 public final int getAndAddInt(Object o, long offset, int delta) {
     int v;
     do {
         //获取内存中最新值
         v = getIntVolatile(o, offset);
       //通过CAS操作
     } while (!compareAndSwapInt(o, offset, v, v + delta));
     return v;
 }

//1.8新增,方法作用同上,只不过这里操作的long类型数据
 public final long getAndAddLong(Object o, long offset, long delta) {
     long v;
     do {
         v = getLongVolatile(o, offset);
     } while (!compareAndSwapLong(o, offset, v, v + delta));
     return v;
 }

 //1.8新增,给定对象o,根据获取内存偏移量对于字段,将其 设置为新值newValue,
 //这是一个CAS操作过程,直到设置成功方能退出循环,返回旧值
 public final int getAndSetInt(Object o, long offset, int newValue) {
     int v;
     do {
         v = getIntVolatile(o, offset);
     } while (!compareAndSwapInt(o, offset, v, newValue));
     return v;
 }

// 1.8新增,同上,操作的是long类型
 public final long getAndSetLong(Object o, long offset, long newValue) {
     long v;
     do {
         v = getLongVolatile(o, offset);
     } while (!compareAndSwapLong(o, offset, v, newValue));
     return v;
 }

 //1.8新增,同上,操作的是引用类型数据
 public final Object getAndSetObject(Object o, long offset, Object newValue) {
     Object v;
     do {
         v = getObjectVolatile(o, offset);
     } while (!compareAndSwapObject(o, offset, v, newValue));
     return v;
 }

上述的方法我们在稍后的Atomic系列分析中还会见到它们的身影。

4.6 挂起与恢复

将一个线程进行挂起是通过park()方法实现的,调用park()后,线程将一直阻塞直到超时或者中断条件出现,unpark()可以终止一个挂起的线程,使其恢复正常,Java对线程的挂起操作被封装在LockSupport类中。LockSupport类中有各种版本pack()方法,其底层实现最终还是使用Unsafe.park()方法和Unsafe.unpark()方法

//线程调用该方法,线程将一直阻塞直到超时,或者是中断条件出现。  
public native void park(boolean isAbsolute, long time);  

//终止挂起的线程,恢复正常.java.util.concurrent包中挂起操作都是在LockSupport类实现的,其底层正是使用这两个方法,  
public native void unpark(Object thread); 

4.7 内存屏障

这里主要包括了loadFence、storeFence、fullFence等方法,这些方法是在Java 8新引入的,用于定义内存屏障,避免代码重排序,与Java内存模型相关。

//在该方法之前的所有读操作,一定在load屏障之前执行完成
public native void loadFence();
//在该方法之前的所有写操作,一定在store屏障之前执行完成
public native void storeFence();
//在该方法之前的所有读写操作,一定在full屏障之前执行完成,这个内存屏障相当于上面两个的合体功能
public native void fullFence();

4.8 其他操作

//获取持有锁,已不建议使用
@Deprecated
public native void monitorEnter(Object var1);
//释放锁,已不建议使用
@Deprecated
public native void monitorExit(Object var1);
//尝试获取锁,已不建议使用
@Deprecated
public native boolean tryMonitorEnter(Object var1);

//获取本机内存的页数,这个值永远都是2的幂次方  
public native int pageSize();  

//告诉虚拟机定义了一个没有安全检查的类,默认情况下这个类加载器和保护域来着调用者类  
public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);  

//加载一个匿名类
public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);
//判断是否需要加载一个类
public native boolean shouldBeInitialized(Class<?> c);
//确保类一定被加载 
public native  void ensureClassInitialized(Class<?> c)

5. 并发包中的原子操作类(Atomic系列)

通过前面的分析我们已经基本了解无锁CAS的原理并对Java中的指针类Unsafe类有了比较全面的认识,下面进一步分析CAS在Java中的应用,即并发包中的原子操作类(Atomic系列),从JDK 1.5开始提供了java.util.concurrent.atomic包,在该包中提供了许多基于CAS实现的原子操作类,用法方便,性能高效,主要分以下4种类型。

5.1 原子更新基本类型

原子更新基本类型主要包括三个类:

  • AtomicBoolean:原子更新布尔类型。
  • AtomicInteger:原子更新整型。
  • AtomicLong:原子更新长整型。
    这三个类的使用方式和实现原理几乎是一样的,这里我们以AtomicInteger为例进行分析,AtomicInteger主要是针对int类型的数据执行原子操作,它提供了原子自增方法、原子自减方法以及原子赋值方法等,鉴于AtomicInteger的源码不多,我们直接看源码
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // 获取指针类Unsafe
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //下述变量value在AtomicInteger 实例对象内的内存偏移量
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
	//当前AtomicInteger封装的int变量value
    private volatile int value;

    //初始化value的值
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    public AtomicInteger() {
    }

    //获取当前最新值
    public final int get() {
        return value;
    }

    //设置当前值,具备volatile效果,方法用final修饰是为了更进一步的保证线程安全。
    public final void set(int newValue) {
        value = newValue;
    }

    //最终会设置成newValue,使用该方法后可能导致其他线程在之后的一段时间内可以获取到旧值,类似于延迟加载
    public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);
    }

    //设置新值并获取旧值,底层调用的是CAS操作即unsafe.compareAndSwapInt()方法
    public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }

     //如果当前值为expect,则设置为update(当前值指的是value变量)
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    
     //当前值加1返回旧值,底层CAS操作
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

     //当前值减1,返回旧值,底层CAS操作
    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }

    //当前值增加delta,返回旧值,底层CAS操作
    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

    //当前值加1,返回新值,底层CAS操作
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

     //当前值减1,返回新值,底层CAS操作
    public final int decrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
    }

   //当前值增加delta,返回新值,底层CAS操作
    public final int addAndGet(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
    }
     //省略一些不常用的方法....
}

通过上述的分析,可以发现AtomicInteger原子类的内部几乎是基于前面分析过Unsafe类中的CAS相关操作的方法实现的,这也同时证明AtomicInteger是基于无锁实现的,这里重点分析自增操作实现过程,其他方法自增实现原理一样。

//当前值加1,返回新值,底层CAS操作
public final int incrementAndGet() {
     return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
 }

我们发现AtomicInteger类中所有自增或自减的方法都间接调用Unsafe类中的getAndAddInt()方法实现了CAS操作,从而保证了线程安全,关于getAndAddInt其实前面已分析过,它是Unsafe类中1.8新增的方法,源码如下

//Unsafe类中的getAndAddInt方法
public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }

可看出getAndAddInt通过一个while循环不断的重试更新要设置的值,直到成功为止,调用的是Unsafe类中的compareAndSwapInt方法,是一个CAS操作方法。这里需要注意的是,上述源码分析是基于JDK1.8的,如果是1.8之前的方法,AtomicInteger源码实现有所不同,是基于for死循环的,如下

//JDK 1.7的源码,由for的死循环实现,并且直接在AtomicInteger实现该方法,
//JDK1.8后,该方法实现已移动到Unsafe类中,直接调用getAndAddInt方法即可
public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

下面简单看个Demo,感受一下AtomicInteger使用方式

public class AtomicIntegerDemo {
    //创建AtomicInteger,用于自增操作
    static AtomicInteger i=new AtomicInteger(0);

    static class  AddThread implements Runnable{

        @Override
        public void run() {
            for (int j = 0; j <10000 ; j++) {
                i.getAndIncrement();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] ts=new Thread[5];
        //开启10条线程同时执行i的自增操作
        for(int k=0;k<ts.length;k++){
            ts[k]=new Thread(new AddThread());
        }
        //启动线程
        for(int k=0;k<ts.length;k++){
            ts[k].start();
        }

        for(int k=0;k<ts.length;k++){
            ts[k].join();
        }

        System.out.println(i);//输出结果:50000
    }
}

在Demo中,使用原子类型AtomicInteger替换普通int类型执行自增的原子操作,保证了线程安全。至于AtomicBoolean和AtomicLong的使用方式以及实现原理是一样,大家可以自行查阅源码。

5.2 原子更新引用

原子更新引用类型可以同时更新引用类型,这里主要分析一下AtomicReference原子类,即原子更新引用类型。先看看其使用方式,如下

public class AtomicReferenceDemo {

    public  static AtomicReference<User> atomicUserRef=new AtomicReference<>();
    public static void main(String[] args) {

        User user = new User("张三", 22);
        atomicUserRef.set(user);
        User updateUser  = new User("李四", 36);
        atomicUserRef.compareAndSet(user,updateUser);
        System.out.println(atomicUserRef.get().toString());
        //输出结果:User{name='李四', age=36}

    }

    static class User {
        public String name;
        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

}

那么AtomicReference原子类内部是如何实现CAS操作的呢?

public class AtomicReference<V> implements java.io.Serializable {
    private static final long serialVersionUID = -1848883965231344442L;

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicReference.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
	//内部变量value,Unsafe类通过valueOffset内存偏移量即可获取该变量
    private volatile V value;

    //使用构造方法初始化value
    public AtomicReference(V initialValue) {
        value = initialValue;
    }

    //延迟加载
    public final void lazySet(V newValue) {
        unsafe.putOrderedObject(this, valueOffset, newValue);
    }

    /**
     * CAS方法,间接调用unsafe.compareAndSwapObject()
     */
    public final boolean compareAndSet(V expect, V update) {
        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
    }
    //设置并获取旧值
    @SuppressWarnings("unchecked")
    public final V getAndSet(V newValue) {
        return (V)unsafe.getAndSetObject(this, valueOffset, newValue);
    }
//省略其他代码......
    }
}



//Unsafe类中的getAndSetObject方法,实际调用还是CAS操作
public final Object getAndSetObject(Object o, long offset, Object newValue) {
      Object v;
      do {
          v = getObjectVolatile(o, offset);
      } while (!compareAndSwapObject(o, offset, v, newValue));
      return v;
  }

从源码看来,AtomicReference与AtomicInteger的实现原理基本是一样的,最终执行的还是Unsafe类,关于AtomicReference的其他方法也是一样的,如下

public final V getAndUpdate(UnaryOperator<V> updateFunction) {
        V prev, next;
        do {
            prev = get();
            next = updateFunction.apply(prev);
        } while (!compareAndSet(prev, next));
        return prev;
    }
    
public final V updateAndGet(UnaryOperator<V> updateFunction) {
        V prev, next;
        do {
            prev = get();
            next = updateFunction.apply(prev);
        } while (!compareAndSet(prev, next));
        return next;
    }

public final V getAndAccumulate(V x,
                                    BinaryOperator<V> accumulatorFunction) {
        V prev, next;
        do {
            prev = get();
            next = accumulatorFunction.apply(prev, x);
        } while (!compareAndSet(prev, next));
        return prev;
    }

public final V accumulateAndGet(V x,
                                    BinaryOperator<V> accumulatorFunction) {
        V prev, next;
        do {
            prev = get();
            next = accumulatorFunction.apply(prev, x);
        } while (!compareAndSet(prev, next));
        return next;
    }

这些方法是Java8新增的,可以基于Lambda表达式对传递进来的期望值或要更新的值进行其他操作后再进行CAS操作,说白了就是对期望值或要更新的值进行额外修改后再执行CAS更新,在所有的Atomic原子类中几乎都存在这几个方法。

5.3 原子更新数组

原子更新数组指的是通过原子的方式更新数组里的某个元素,主要有以下三个类

  • AtomicIntegerArray:原子更新整数数组里的元素。
  • AtomicLongArray:原子更新长整数数组里的元素。
  • AtomicReferenceArray:原子更新引用类型数组里的元素
    这里以AtomicIntegerArray为例进行分析,其余两个使用方式和实现原理基本一样,简单案例如下,
public class AtomicIntegerArrayDemo {
   static AtomicIntegerArray array=new AtomicIntegerArray(10);

    static class AddThread implements Runnable{

        @Override
        public void run() {
            for (int i = 0; i <10000 ; i++) {
                array.getAndIncrement(i%array.length());
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] ts=new Thread[10];
        //创建10条线程
        for(int k=0;k<ts.length;k++){
            ts[k]=new Thread(new AddThread());
        }
        //启动10条线程
        for(int k=0;k<ts.length;k++){
            ts[k].start();
        }
        for(int k=0;k<ts.length;k++){
            ts[k].join();
        }
        //执行结果
        //[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]
        System.out.println(array);

    }
}

启动10条线程对数组中的元素进行自增操作,执行结果符合预期。使用方式比较简单,接着看看AtomicIntegerArray内部是如何实现,先看看部分源码

public class AtomicIntegerArray implements java.io.Serializable {
    //获取unsafe类的实例对象
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //获取数组的第一个元素内存起始地址
    private static final int base = unsafe.arrayBaseOffset(int[].class);

    private static final int shift;
    //内部数组
    private final int[] array;

    static {
        //获取数组中一个元素占据的内存空间
        int scale = unsafe.arrayIndexScale(int[].class);
        //判断是否为2的次幂,一般为2的次幂否则抛异常
        if ((scale & (scale - 1)) != 0)
            throw new Error("data type scale not a power of two");
        //
        shift = 31 - Integer.numberOfLeadingZeros(scale);
    }

    private long checkedByteOffset(int i) {
        if (i < 0 || i >= array.length)
            throw new IndexOutOfBoundsException("index " + i);

        return byteOffset(i);
    }
    //计算数组中每个元素的的内存地址
    private static long byteOffset(int i) {
        return ((long) i << shift) + base;
    }
    //省略其他代码......
}

通过前面对Unsafe类的分析,我们知道arrayBaseOffset方法可以获取数组的第一个元素起始地址,而arrayIndexScale方法可以获取每个数组元素占用的内存空间,由于这里是Int类型,而Java中一个int类型占用4个字节,也就是scale的值为4,那么如何根据数组下标值计算每个元素的内存地址呢?显然应该是

每个数组元素的内存地址=起始地址+元素下标 * 每个元素所占用的内存空间

与该方法原理相同

//计算数组中每个元素的的内存地址
private static long byteOffset(int i) {
     return ((long) i << shift) + base;
 }

这是为什么,首先来计算出shift的值

 shift = 31 - Integer.numberOfLeadingZeros(scale);

其中Integer.numberOfLeadingZeros(scale)是计算出scale的前导零个数(必须是连续的),scale=4,转成二进制为
00000000 00000000 00000000 00000100
即前导零数为29,也就是shift=2,然后利用shift来定位数组中的内存位置,在数组不越界时,计算出前3个数组元素内存地址

//第一个数组元素,index=0 , 其中base为起始地址,4代表int类型占用的字节数 
address = base + 0 * 4 即address= base + 0 << 2
//第二个数组元素,index=1
address = base + 1 * 4 即address= base + 1 << 2
//第三个数组元素,index=2
address = base + 2 * 4 即address= base + 2 << 2
//........

显然shift=2,替换去就是

address= base + i << shift

这就是 byteOffset(int i) 方法的计算原理。因此byteOffset(int)方法可以根据数组下标计算出每个元素的内存地址。至于其他方法就比较简单了,都是间接调用Unsafe类的CAS原子操作方法,如下简单看其中几个常用方法

//执行自增操作,返回旧值,i是指数组元素下标
public final int getAndIncrement(int i) {
      return getAndAdd(i, 1);
}
//指定下标元素执行自增操作,并返回新值
public final int incrementAndGet(int i) {
    return getAndAdd(i, 1) + 1;
}

//指定下标元素执行自减操作,并返回新值
public final int decrementAndGet(int i) {
    return getAndAdd(i, -1) - 1;
}
//间接调用unsafe.getAndAddInt()方法
public final int getAndAdd(int i, int delta) {
    return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
}

//Unsafe类中的getAndAddInt方法,执行CAS操作
public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }

至于AtomicLongArray和AtomicReferenceArray原子类,使用方式和实现原理基本一样。

5.4 原子更新属性

我们我们只需要某个类里的某个字段,也就是说让普通的变量也享受原子操作,可以使用原子更新字段类,如在项目前期考虑不周全,项目需求又发生变化,使得某个类中的变量需要执行多线程操作,由于该变量多出引用,改动起来比较麻烦,而且用了使用的地方无需使用线程安全,只要求新场景需要使用时,可以借助原子更新器处理这种场景,Atomic并发包提供了以下三个类:

  • AtomicIntegerFiledUpdater:原子更新整型字段的更新器。
  • AtomicLongFiledUpdater:原子更新长整型字段的更新器。
  • AtomicReferenceFileUpdater:原子更新引用类型字段的更新器。

请注意原子更新器的使用存在比较苛刻的条件如下

  • 操作的字段不能是static类型。
  • 操作的字段不能是final类型的。因为final的变量不能修改。
  • 字段必须是volatile修饰的。也就是数据本身是读一致的。
  • 属性必须对当前的Updater所在的区域是可见的,如果不是当前类内部进行原子更新器操作不能使用private,子类操作父类时必须是protected权限及以上,如果在同一个包下则必须是default权限及以上,也就是说,无论何时都要保证操作类与被操作类间的可见性。

下面看看AtomicIntegerFieldUpdater和AtomicReferenceFieldUpdater的简单使用方式

public class AtomicIntegerFieldUpdaterDemo {

    public static class Candidate{
        int id;
        volatile int score;
    }

    public static class Game{
        int id;
        volatile String name;

        public Game(int id, String name) {
            this.id = id;
            this.name = name;
        }

        @Override
        public String toString() {
            return "Game{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    //初始化score整型更新器
   static AtomicIntegerFieldUpdater<Candidate> atIntegerUpdater
           = AtomicIntegerFieldUpdater.newUpdater(Candidate.class,"score");
    //初始化name引用类型更新器
    static AtomicReferenceFieldUpdater<Game,String> atRefUpdate
            =AtomicReferenceFieldUpdater.newUpdater(Game.class,String.class,"name");

    //用于验证分数是否正确
    public static AtomicInteger allScore=new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        final Candidate stu=new Candidate();
        Thread[] t=new Thread[10000];

        //开启10000个线程
        for(int i = 0 ; i < t.length ; i++) {
            t[i]=new Thread() {
                public void run() {
                    if(Math.random()>0.4){
                        atIntegerUpdater.incrementAndGet(stu);
                        allScore.incrementAndGet();
                    }
                }
            };
            t[i].start();
        }

        for(int i = 0 ; i < t.length ; i++) {
            t[i].join();
        }

        System.out.println("最终分数score="+stu.score);
        System.out.println("校验分数allScore="+allScore);

        //AtomicReferenceFieldUpdater 简单的使用
        Game game = new Game(2,"zh");
        atRefUpdate.compareAndSet(game,game.name,"JAVA");
        System.out.println(game.toString());

        /**
         * 输出结果:
         最终分数score=6012
         校验分数allScore=6012
         Game{id=2, name='JAVA'}
         */
    }
}

我们使用AtomicIntegerFieldUpdater更新候选人(Candidate)的分数score,开启了10000条线程投票,当随机值大于0.4时算一票,分数自增一次,其中allScore用于验证分数是否正确(其实用于验证AtomicIntegerFieldUpdater更新的字段是否线程安全),当allScore与score相同时,则说明投票结果无误,也代表AtomicIntegerFieldUpdater能正确更新字段score的值,是线程安全的。对于AtomicReferenceFieldUpdater,我们在代码中简单演示了其使用方式,注意在AtomicReferenceFieldUpdater注明泛型时需要两个泛型参数,一个是修改的类类型,一个修改字段的类型。至于AtomicLongFieldUpdater则与AtomicIntegerFieldUpdater类似,不再介绍。接着简单了解一下AtomicIntegerFieldUpdater的实现原理,实际就是反射和Unsafe类结合,AtomicIntegerFieldUpdater是个抽象类,实际实现类为AtomicIntegerFieldUpdaterImpl

public abstract class AtomicIntegerFieldUpdater<T> {

    public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
                                                              String fieldName) {
         //实际实现类AtomicIntegerFieldUpdaterImpl                                          
        return new AtomicIntegerFieldUpdaterImpl<U>
            (tclass, fieldName, Reflection.getCallerClass());
    }
 }

看看AtomicIntegerFieldUpdaterImpl

 private static class AtomicIntegerFieldUpdaterImpl<T>
            extends AtomicIntegerFieldUpdater<T> {
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private final long offset;//内存偏移量
        private final Class<T> tclass;
        private final Class<?> cclass;

        AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
                                      final String fieldName,
                                      final Class<?> caller) {
            final Field field;//要修改的字段
            final int modifiers;//字段修饰符
            try {
                field = AccessController.doPrivileged(
                    new PrivilegedExceptionAction<Field>() {
                        public Field run() throws NoSuchFieldException {
                            return tclass.getDeclaredField(fieldName);//反射获取字段对象
                        }
                    });
                    //获取字段修饰符
                modifiers = field.getModifiers();
            //对字段的访问权限进行检查,不在访问范围内抛异常
                sun.reflect.misc.ReflectUtil.ensureMemberAccess(
                    caller, tclass, null, modifiers);
                ClassLoader cl = tclass.getClassLoader();
                ClassLoader ccl = caller.getClassLoader();
                if ((ccl != null) && (ccl != cl) &&
                    ((cl == null) || !isAncestor(cl, ccl))) {
              sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
                }
            } catch (PrivilegedActionException pae) {
                throw new RuntimeException(pae.getException());
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }

            Class<?> fieldt = field.getType();
            //判断是否为int类型
            if (fieldt != int.class)
                throw new IllegalArgumentException("Must be integer type");
            //判断是否被volatile修饰
            if (!Modifier.isVolatile(modifiers))
                throw new IllegalArgumentException("Must be volatile type");

            this.cclass = (Modifier.isProtected(modifiers) &&
                           caller != tclass) ? caller : null;
            this.tclass = tclass;
            //获取该字段的在对象内存的偏移量,通过内存偏移量可以获取或者修改该字段的值
            offset = unsafe.objectFieldOffset(field);
        }
        }

从AtomicIntegerFieldUpdaterImpl的构造器也可以看出更新器为什么会有这么多限制条件了,当然最终其CAS操作肯定是通过unsafe完成的,简单看一个方法

public int incrementAndGet(T obj) {
        int prev, next;
        do {
            prev = get(obj);
            next = prev + 1;
            //CAS操作
        } while (!compareAndSet(obj, prev, next));
        return next;
}

//最终调用的还是unsafe.compareAndSwapInt()方法
public boolean compareAndSet(T obj, int expect, int update) {
            if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
            return unsafe.compareAndSwapInt(obj, offset, expect, update);
        }

6. CAS 的缺点?

6.1 CPU开销过大

对于资源竞争严重(线程冲突)的情况,CAS自旋的概率会比较大,从而会浪费更多的CPU资源。效率低于 synchronized。如以下代码

 //1.8新增,给定对象o,根据获取内存偏移量指向的字段,将其增加delta,
 //这是一个CAS操作过程,直到设置成功方能退出循环,返回旧值
 public final int getAndAddInt(Object o, long offset, int delta) {
     int v;
     do {
         //获取内存中最新值
         v = getIntVolatile(o, offset);
       //通过CAS操作
     } while (!compareAndSwapInt(o, offset, v, v + delta));
     return v;
 }

解决办法:
破坏掉死循环,当超出一定时间或者一定次数时,return退出。JDK8新增的LongAddr,和ConcurrentHashMap类似的方法。当多个线程竞争时,将粒度变小,将一个变量拆分为多个变量,达到多个线程访问多个资源的效果,最后再调用sum把它合起来。

6.2 多变量共享一致性问题

当对一个共享变量进行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量进行操作时,循环CAS就无法保证操作的原子性了。

解决办法:

  • 可以加锁来解决。
  • 可以使用封装成对象使用AtomicReference类来解决

6.3 ABA问题

什么是ABA问题?
假设这样一个场景,当第一个线程执行CAS(V,A,B)操作,在获取到当前变量V,准备修改为新值B之前,另外两个线程已经连续修改了两次变量V的值,使得该值又恢复为旧值,这样的话,我们就无法正确判断这个变量是否已被修改过,如下图
在这里插入图片描述
可能大家还是对ABA问题还不够了解,那么下面我举个例子详细说明一下。

假如你去买200元的压缩皮肤,此时银行卡账户有1000元,但是由于网络和操作问题,致购买操作同时提交了两遍,后台开启了两个线程(线程1、线程2),两个线程都是获取当前值1000元,要更新成800元;理想情况下,应该一个线程更新成功,一个线程更新失败,我的存款只扣除一次,也就是余额应为800元 。
但是刚好这时候,你好兄弟欠你的200块钱给你转到卡里了(线程3)。然后发生ABA问题,此时卡里余额也只有800元。你会发现少了200元,那是不可能容忍的。

事情是这样的

  1. 线程1首先执行成功,把余额1000更新为800(取钱线程)。
  2. 同时线程2由于某种原因陷入了阻塞状态(取钱线程)。
  3. 这时候,(线程3)好兄弟汇款给了我200元,执行成功,我的账户800更新为1000元。
    过一会儿,线程2恢复运行,由于之前阻塞的时候获得了当前值为:1000,并且经过compare检测,此时存款也的确是1000元,所以又成功把变量值从1000更新成了800
  4. 好兄弟发微信跟你说欠你的200元给你打到卡里了,
  5. 你看到账户余额的800元陷入了沉思。

这就是典型的CAS的ABA问题,发生后可能造成严重的问题。那么该如何解决呢?在Java中解决ABA问题,我们可以使用以下两个原子类

  • AtomicStampedReference

AtomicStampedReference原子类是一个带有时间戳的对象引用,在每次修改后,AtomicStampedReference不仅会设置新值,而且还会记录更改的时间,当AtomicStampedReference设置对象值时,对象值和时间戳都必须满足期望值才能写入成功,这也就解决了反复读写时,无法预知值是否已被修改的窘境,测试demo如下

//使用AtomicInteger模拟ABA问题,并使用AtomicStampedReference解决问题。
public class ABADemo {
    //初始化账户为1000元
    static AtomicInteger atAccount=new AtomicInteger(1000);

    //初始化时需要传入一个初始值和初始时间,此时为1000和0
    static AtomicStampedReference<Integer> atomicStampedRef =
            new AtomicStampedReference<Integer>(1000,0);

    static  Thread t1=new Thread(()->{

        /**
         * 线程1:用户花了200元,把账户更新为800元
         */
        atAccount.compareAndSet(1000,800);
        /**
         * 线程3:用好兄弟给我转了200元,把账户更新为1000元
         */
        atAccount.compareAndSet(800,1000);
    });

    static Thread t2=new Thread(()->{
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //因为发起两个扣款操作,但是线程2阻塞1s才执行
        boolean flag=atAccount.compareAndSet(1000,800);
        System.out.println("flag:"+flag+",newValue:"+atAccount);
    });


    static  Thread t3=new Thread(()->{
        int time = atomicStampedRef.getStamp();
        //因为是用==比较,比较的是地址,所以取得引用
        Integer value = atomicStampedRef.getReference();
        /**
         * 线程1:用户花了200元,把账户更新为800元
         */
        atomicStampedRef.compareAndSet(value,800,time,time+1);

        time = atomicStampedRef.getStamp();
        value = atomicStampedRef.getReference();
        /**
         * 线程3:用好兄弟给我转了200元,把账户更新为1000元
         */
        atomicStampedRef.compareAndSet(value,1000,time,time+1);


    });

    static  Thread t4=new Thread(()->{
        int time = atomicStampedRef.getStamp();
        System.out.println("sleep前t4线程 time:"+time);
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //expectStamp取0是因为atomicStampedRef初始状态的stamp是0,reference是1000;这里就是为了演示ABA后再修改初始A
        int expectStamp = 0;
        Integer value = atomicStampedRef.getReference();
        //因为发起两个扣款操作,但是线程2阻塞3s才执行
        boolean flag= atomicStampedRef.compareAndSet(value,800,expectStamp,time+1);
        System.out.println("flag:"+flag+",newValue:"+atomicStampedRef.getReference());
    });


    public static void main(String[] args) throws InterruptedException {
        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println("=========分割线==============");

        t3.start();
        t4.start();
        /**
         * flag:true,newValue:800
         * =========分割线==============
         * sleep前t4线程 time:0
         * flag:false,newValue:1000
         */
    }

对比输出结果可知,AtomicStampedReference类确实解决了ABA的问题,下面我们简单看看其内部实现原理

public class AtomicStampedReference<V> {
	//通过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);
        }
    }
	//存储数据和时间戳的内部类
    private volatile Pair<V> pair; 

	 //有参构造器,创建时需传入数据初始值和时间初始值
    public AtomicStampedReference(V initialRef, int initialStamp) {
        pair = Pair.of(initialRef, initialStamp);
    }
 }

接着看看其compareAndSet方法的实现:

public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

同时对当前数据和当前时间进行比较,只有当两者都相等了才会执行casPair()方法,这是一个CAS操作方法,最终调用的还是Unsafe类中的compareAndSwapObject方法

这里大家需要一下,因为expectedReference == current.reference是用==比较,比较的是内存地址,需要需要使用当前对象的地址来作为期望值(Integer value = atomicStampedRef.getReference();

private boolean casPair(Pair<V> cmp, Pair<V> val) {
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    }

总结:

通过分析AtomicStampedReference源码可以发现,在内部封装了一个静态内部类Pair,在初始化的时候通过Pair存储数据和时间戳,在更新时对数据和时间戳进行比较(注意是引用地址的比较),只有二者都符合预期了才会调用Unsafe的CompareAndSwapObject方法进行数据和时间戳的替换,从而避免了ABA问题。

  • AtomicMarkableReference

AtomicMarkableReference与AtomicStampedReference不同的是,AtomicMarkableReference维护的是一个boolean值的标识,也就是说至于true和false两种切换状态,经过博主测试,这种方式并不能完全防止ABA问题的发生,只能减少ABA问题发生的概率。

public class ABAMarkDemo {
    static AtomicMarkableReference<Integer> atMarkRef =
            new AtomicMarkableReference<Integer>(1000,true);



    static  Thread t1=new Thread(()->{

        /**
         * 线程1:用户花了200元,把账户更新为800元
         */
        boolean mark=atMarkRef.isMarked();
        System.out.println("线程1 mark:"+mark);
        boolean flag=atMarkRef.compareAndSet(atMarkRef.getReference(), 800,mark,!mark);
        System.out.println("线程1 flag:"+flag+",newValue:"+atMarkRef.getReference());
        /**
         * 线程3:用好兄弟给我转了200元,把账户更新为1000元
         */
        mark=atMarkRef.isMarked();
        System.out.println("线程3 mark:"+mark);
        flag=atMarkRef.compareAndSet(atMarkRef.getReference(), 1000,mark,!mark);
        System.out.println("线程3 flag:"+flag+",newValue:"+atMarkRef.getReference());
    });

    static Thread t2=new Thread(()->{
        //为了保证同时操作,这里设置为true
        boolean mark=true;
        System.out.println("线程2 sleep前t2 mark:"+mark);
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //因为发起两个扣款操作,但是线程2阻塞3s才执行
        boolean flag=atMarkRef.compareAndSet(atMarkRef.getReference(),800,mark,!mark);
        System.out.println("线程2 flag:"+flag+",newValue:"+atMarkRef.getReference());
    });


    public static  void  main(String[] args) throws InterruptedException {
        t1.start();
        t2.start();


        /**
         * 输出结果:
         线程2 sleep前t2 mark:true
         线程1 mark:true
         线程1 flag:true,newValue:800
         线程3 mark:false
         线程3 flag:true,newValue:1000
         线程2 flag:true,newValue:800---->成功了.....说明还是发生ABA问题
         */
    }

}

AtomicMarkableReference的实现原理与AtomicStampedReference类似,这里不再介绍。到此,我们也明白了如果要完全杜绝ABA问题的发生,我们应该使用AtomicStampedReference原子类更新对象,而对于AtomicMarkableReference来说只能减少ABA问题的发生概率,并不能杜绝。

6.4 自旋锁

自旋锁是一种假设在不久的将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是被称为自旋的原因),在经过若干次循环后,如果得到锁,则顺利进入临界区,如果还不能获得锁,那将会将线程在操作系统层面挂起,这种方式也是可以提升效率的,但是当线程越来越多竞争很激烈时,占用CPU的时间就会变长,导致性能急剧下降,因此Java虚拟机内部一般对于自旋锁有一定的次数限制,可能是50或者100次循环后就放弃,直接挂起线程,让出CPU资源。如下通过AtomicReference可实现简单的自旋锁。

public class SpinLock {
  private AtomicReference<Thread> sign =new AtomicReference<>();

  public void lock(){
    Thread current = Thread.currentThread();
    while(!sign .compareAndSet(null, current)){
    }
  }

  public void unlock (){
    Thread current = Thread.currentThread();
    sign .compareAndSet(current, null);
  }
}

使用CAS原子操作作为底层实现,lock()方法将要更新的值设置为当前线程,并将预期值设置为null。unlock()函数将要更新的值设置为null,并预期值设置为当前线程。然后我们通过lock()和unlock来控制自旋锁的开启与关闭,注意这是一种非公平锁。事实上AtomicInteger(或者AtomicLong)原子类内部的CAS操作也是通过不断的自循环(while循环)实现,不过这种循环的结束条件是线程成功更新对于的值,但也是自旋锁的一种。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值