Java中Atomic原子类型的详细讲解(二)-刘宇

  • 一、AtomicReference介绍
  • 1、简述
  • 2、简单使用
  • 二、CompareAndSwap算法带来的ABA问题
  • 1、问题概述
  • 2、ABA问题之队列
  • 三、AtomicStampedReference介绍
  • 1、原理
  • 2、源码解释
  • 3、练习
  • 四、AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray介绍
  • 1、创建
  • 2、get()
  • 3、set()
  • 4、getAndSet()
  • 5、compareAndSet()
  • 五、AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater介绍
  • 1、使用前提
  • 2、使用场景
  • 3、简单的使用
  • 4、异常汇总
  • 4.1、访问私有字段
  • 4.2、访问null对象
  • 4.3、访问不存在的属性
  • 4.4、访问属性的类型不对应
  • 4.5、访问的属性没有使用volatile修饰


作者:刘宇

一、AtomicReference介绍

1、简述

  • AtomicReference类提供了一个可以原子读写的对象引用变量。

2、简单使用

package com.brycen.concurrency03.atomic;

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceTest {

	public static void main(String[] args) {
		AtomicReference<Simple> atomic = new AtomicReference<Simple>(new Simple("liuyu", 24));
		//获取当前值
		System.out.println(atomic.get());
		
		//对比并设置值,如果参数1不等于当前值则赋值失败,否则则成功。
		boolean result = atomic.compareAndSet(new Simple("liuyu11", 24), new Simple("Brycen", 24));
		System.out.println(result);
	}
	
	static class Simple {
		private String name;
		private int age;
		public Simple(String name, int age) {
			super();
			this.name = name;
			this.age = age;
		}
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public int getAge() {
			return age;
		}
		public void setAge(int age) {
			this.age = age;
		}
		@Override
		public String toString() {
			return "Simple [name=" + name + ", age=" + age + "]";
		}
	}
}

输出结果:

Simple [name=liuyu, age=24]
false

二、CompareAndSwap算法带来的ABA问题

不清楚什么是CAS算法的童鞋们可以看我上一篇博客

1、问题概述

ABA问题就是,当有两个线程对其修改时,线程1在修改之前的值是A,需要修改成B,但是由于此时线程2获得了CPU执行权,将元素值修改成了B,然后又修改成了A。那么当线程1再次获取到CPU执行权的时候进行CAS算法对比时,线程1并未发现此时元素已发生变化,因为他的值是和CAS算法对比之前的值是一样的,这就是ABA问题。ABA问题在int、long等类型并无影响,但是在类似于队列等就会引起严重问题。

2、ABA问题之队列

java原子性集合 java原子类型_高并发


流程解释:

当线程T2执行完第二步之后将CPU执行权交于T1,而在T1执行第三步弹出A元素之前会进行CAS算法校验。然后发现此时队列中的head元素A存在,即CAS算法通过,即认为队列没有发生变化,那么此时T1线程拿到的队列还是T2操作之前的队列,并没有更新,则进行了第三步弹出A,那么此时队列的队首就会变为B,而B元素在T2线程的时候已经变为了游离状态,那么此时就会出现严重错误。

三、AtomicStampedReference介绍

AtomicStampedReference可以完美解决上述的ABA问题

1、原理

利用stamp印记来表示当前更新的版本,每次更新一次stamp就会+1,在CAS算法对比的时候不光要对比其值是否一致,还需要对比印记编号是否一致,这样就有效解决了ABA问题。

2、源码解释

片段1:

  • 就是将要存入的值或引用和stamp更新标签用一个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);
    }
}

片段2:

  • expectedReference == current.reference判断当前值或引用和你所传进来的希望值或引用是否一致
  • expectedStamp == current.stamp判断当前更新标记和你所传进来的希望更新标记是否一致
  • (newReference == current.reference && newStamp == current.stamp) 判断新值或引用是否和旧的一致,并且判断其更新标记是否一致,如果都一致则不会执行casPair来更新,如果上面为false则执行casPair来更新。因为他们是用||来连接的
  • casPair(current, Pair.of(newReference, newStamp)更新
public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;//获取当前的pair对象
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

3、练习

package com.brycen.concurrency03.atomic;

import java.util.concurrent.atomic.AtomicStampedReference;

public class AtomicStampedReferenceTest {
	private static AtomicStampedReference<Integer> atomicRef = new AtomicStampedReference<Integer>(100, 0);
	
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(1000);
					//将值100修改为101
					boolean success = atomicRef.compareAndSet(100, 101, atomicRef.getStamp(), atomicRef.getStamp()+1);
					System.out.println(success);
					//再将101修改为100
					success = atomicRef.compareAndSet(101, 100, atomicRef.getStamp(), atomicRef.getStamp()+1);
					System.out.println(success);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					int stamp = atomicRef.getStamp();
					Thread.sleep(2000);
					//这边使用了AtomicStampedReference,所以它发现了更新版本号不对,返回false禁止更新,如果使用不带stamped的atomic则会出现更新成功的现象,即ABA问题。
					boolean success = atomicRef.compareAndSet(100, 101, stamp, stamp+1);
					System.out.println(success);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
		t1.start();
		t2.start();
		t1.join();
		t2.join();
	}
}

输出结果

true
true
false

四、AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray介绍

  • 是能够实现对数组支持原子性操作的
  • 这里的AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray和前面介绍的非数组的使用差别不大,这边就简单演示一下AtomicIntegerArray的基本用法。基本类型的非数组的使用方法可以查看我的上一篇博客。
  • 其实就是比非数组的多了一个传递角标的参数。

1、创建

//第一种创建方式
//指定其数组长度为10
AtomicIntegerArray array = new AtomicIntegerArray(10);
System.out.println(array.length());//输出10
//第二种创建方式
//创建一个数组传递进去
int[] intArray = new int[20];
AtomicIntegerArray array1 = new AtomicIntegerArray(intArray);
System.out.println(array1.length());//输出20

2、get()

AtomicIntegerArray array = new AtomicIntegerArray(10);
//获取第5个元素的值
System.out.println(array.get(5));//输出0

3、set()

AtomicIntegerArray array = new AtomicIntegerArray(10);
//将数组中角标为5的元素设置为100
array.set(5,100);
System.out.println(array.get(5));//输出100

4、getAndSet()

AtomicIntegerArray array = new AtomicIntegerArray(10);
int result = array.getAndSet(5, 200);
System.out.println(result);//输出0
System.out.println(array.get(5));//输出200

5、compareAndSet()

AtomicIntegerArray array = new AtomicIntegerArray(10);
boolean result = array.compareAndSet(5, 0, 100);
System.out.println(result);//输出true
System.out.println(array.get(5));//输出100

其他方法用法和非数组的原子基本类型操作差不多,可参考我上一篇博客,这里就简单介绍了AtomicIntegerArray的一些方法

五、AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater介绍

  • 当多线程访问我们类属性的时候,我们可以使用加锁的形式来保证其安全性。那么如果使用无锁的形式的话就可以使用我们的原子类型的FieldUpdater类。
  • 原子类型的FieldUpdater类大致分为了AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater,他们之间的用法都大差不差,看过之前的博客的肯定都没问题,这边我们就简单演示一下AtomicIntegerFieldUpdater和AtomicReferenceFieldUpdater。

1、使用前提

  • 属性必须使用volatile关键字修饰
  • 属性必须是非private、protected(如果是当前类是可以的)
  • 属性类型必须和原子类中的类型一致

2、使用场景

  • 不想使用锁的时候(包括显示锁或者重量级锁synchronized)
  • 大量需要原子类型修饰的对象,如果整体对象使用AtomicStampReference修饰或者属性使用AtomicInteger修饰,都相比较需要消耗大量内存。

3、简单的使用

  • 保证了类中属性的原子性
package com.test.atomic;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterTest {
    public static void main(String[] args) {
        AtomicIntegerFieldUpdater<TestMe> updater = AtomicIntegerFieldUpdater.newUpdater(TestMe.class,"i");
        TestMe me = new TestMe();
        for (int i=0;i<2;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i=0;i<5;i++){
                        int v = updater.getAndIncrement(me);
                        System.out.println(Thread.currentThread().getName()+"->"+v);
                    }
                }
            }).start();
        }
    }

    static class TestMe{
        volatile  int i;
    }
}

输出结果:

Thread-0->0
Thread-0->1
Thread-0->2
Thread-0->3
Thread-0->4
Thread-1->5
Thread-1->6
Thread-1->7
Thread-1->8
Thread-1->9

4、异常汇总

4.1、访问私有字段

  • 如果访问私有变量则会抛出异常
package com.test.atomic;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterFailedTest {
    public static void main(String[] args) {
        AtomicIntegerFieldUpdater<TestMe> updater = AtomicIntegerFieldUpdater.newUpdater(TestMe.class,"i");
        TestMe me = new TestMe();
        updater.compareAndSet(me,0,1);
    }
    static class TestMe{
        private volatile  int i;
    }
}

输出结果:

Exception in thread "main" java.lang.RuntimeException: java.lang.IllegalAccessException: Class com.test.atomic.AtomicIntegerFieldUpdaterFailedTest can not access a member of class com.test.atomic.AtomicIntegerFieldUpdaterFailedTest$TestMe with modifiers "private volatile"
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.<init>(AtomicIntegerFieldUpdater.java:405)
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdater.java:88)
	at com.test.atomic.AtomicIntegerFieldUpdaterFailedTest.main(AtomicIntegerFieldUpdaterFailedTest.java:7)
Caused by: java.lang.IllegalAccessException: Class com.test.atomic.AtomicIntegerFieldUpdaterFailedTest can not access a member of class com.test.atomic.AtomicIntegerFieldUpdaterFailedTest$TestMe with modifiers "private volatile"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at sun.reflect.misc.ReflectUtil.ensureMemberAccess(ReflectUtil.java:103)
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.<init>(AtomicIntegerFieldUpdater.java:394)
	... 2 more

4.2、访问null对象

package com.test.atomic;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterFailedTest {
    public static void main(String[] args) {
        AtomicIntegerFieldUpdater<TestMe> updater = AtomicIntegerFieldUpdater.newUpdater(TestMe.class,"i");
        updater.compareAndSet(null,0,1);
    }
    static class TestMe{
        public volatile  int i;
    }
}

输出结果:

Exception in thread "main" java.lang.ClassCastException
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.throwAccessCheckException(AtomicIntegerFieldUpdater.java:475)
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.accessCheck(AtomicIntegerFieldUpdater.java:466)
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.compareAndSet(AtomicIntegerFieldUpdater.java:488)
	at com.test.atomic.AtomicIntegerFieldUpdaterFailedTest.main(AtomicIntegerFieldUpdaterFailedTest.java:8)

4.3、访问不存在的属性

  • 在TestMe类中我们的属性是i,而我们更新的属性是i1,则会出现异常。
package com.test.atomic;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterFailedTest {
    public static void main(String[] args) {
        AtomicIntegerFieldUpdater<TestMe> updater = AtomicIntegerFieldUpdater.newUpdater(TestMe.class,"i1");
        updater.compareAndSet(null,0,1);
    }
    static class TestMe{
        public volatile  int i;
    }
}

输出结果:

Exception in thread "main" java.lang.RuntimeException: java.lang.NoSuchFieldException: i1
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.<init>(AtomicIntegerFieldUpdater.java:403)
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdater.java:88)
	at com.test.atomic.AtomicIntegerFieldUpdaterFailedTest.main(AtomicIntegerFieldUpdaterFailedTest.java:7)
Caused by: java.lang.NoSuchFieldException: i1

4.4、访问属性的类型不对应

  • 可以看到TestMe中的属性类型是Integer类型,如果我们给的是Long类型则会报错
package com.test.atomic;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class AtomicIntegerFieldUpdaterFailedTest {
    public static void main(String[] args) {
        AtomicReferenceFieldUpdater<TestMe,Long> updater = AtomicReferenceFieldUpdater.newUpdater(TestMe.class,Long.class,"i");
        TestMe me = new TestMe();
        boolean result = updater.compareAndSet(me, null, 1l);
        System.out.println(result);
    }

    static class TestMe{
        public volatile  Integer i;
    }
}

输出结果:

Exception in thread "main" java.lang.ClassCastException
	at java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl.<init>(AtomicReferenceFieldUpdater.java:343)
	at java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(AtomicReferenceFieldUpdater.java:110)
	at com.test.atomic.AtomicIntegerFieldUpdaterFailedTest.main(AtomicIntegerFieldUpdaterFailedTest.java:8)

4.5、访问的属性没有使用volatile修饰

  • 可以看到我们TestMe中的属性i没有加volatile关键字,那么就会出错。
package com.test.atomic;

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class AtomicIntegerFieldUpdaterFailedTest {
    public static void main(String[] args) {
        AtomicReferenceFieldUpdater<TestMe,Integer> updater = AtomicReferenceFieldUpdater.newUpdater(TestMe.class,Integer.class,"i");
        TestMe me = new TestMe();
        boolean result = updater.compareAndSet(me, null, 1);
        System.out.println(result);
    }
    static class TestMe{
        public Integer i;
    }
}

输出结果:

Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type
	at java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl.<init>(AtomicReferenceFieldUpdater.java:348)
	at java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(AtomicReferenceFieldUpdater.java:110)
	at com.test.atomic.AtomicIntegerFieldUpdaterFailedTest.main(AtomicIntegerFieldUpdaterFailedTest.java:8)