多线程原子性问题的产生和解决
原子变量:在 java.util.concurrent.atomic 包下提供了一些原子变量。
1. volatile 保证内存可见性,可以查看atomic中变量是使用volatile来进行修饰的:
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; /** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public AtomicInteger(int initialValue) { value = initialValue; }
2. CAS(Compare-And-Swap)比较并交换,算法保证数据变量的原子性
CAS 算法是硬件对于并发操作的支持
CAS 包含了三个操作数:
①内存值 V
②预估值 A
③更新值 B
当且仅当 V == A 时, V = B; 否则,不会执行任何操作。
模拟CAS算法:
/* * 模拟 CAS 算法 */ public class TestCompareAndSwap { public static void main(String[] args) { final CompareAndSwap cas = new CompareAndSwap(); for (int i = 0; i < 10; i++) { new Thread(new Runnable() { @Override public void run() { int expectedValue = cas.get(); boolean b = cas.compareAndSet(expectedValue, (int)(Math.random() * 101)); System.out.println(b); } }).start(); } } } class CompareAndSwap{ private int value; //获取内存值 public synchronized int get(){ return value; } //比较 public synchronized int compareAndSwap(int expectedValue, int newValue){ int oldValue = value; if(oldValue == expectedValue){ this.value = newValue; } return oldValue; } //设置 public synchronized boolean compareAndSet(int expectedValue, int newValue){ return expectedValue == compareAndSwap(expectedValue, newValue); } }
其他博文关于CAS的详细描述:http://blog.csdn.net/ls5718/article/details/52563959
- AtomicBoolean 可以用原子方式更新的 boolean 值。
- AtomicInteger 可以用原子方式更新的 int 值。
- AtomicIntegerArray 可以用原子方式更新其元素的 int 数组。
- AtomicIntegerFieldUpdater<T> 基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新。
- AtomicLong 可以用原子方式更新的 long 值。
- AtomicLongArray 可以用原子方式更新其元素的 long 数组。
- AtomicLongFieldUpdater<T> 基于反射的实用工具,可以对指定类的指定 volatile long 字段进行原子更新。
- AtomicMarkableReference<V> AtomicMarkableReference 维护带有标记位的对象引用,可以原子方式对其进行更新。
- AtomicReference<V> 可以用原子方式更新的对象引用。
- AtomicReferenceArray<E> 可以用原子方式更新其元素的对象引用数组。
- AtomicReferenceFieldUpdater<T,V> 基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。
- AtomicStampedReference<V> AtomicStampedReference 维护带有整数“标志”的对象引用,可以用原子方式对其进行更新。
先看下AtomicInteger:
import java.util.concurrent.atomic.AtomicInteger; /** * Created by soyoungboy on 2017/3/28. */ public class AtomicDemo { public static void main(String[] args) { testAtomicInteger(); } private static void testAtomicInteger() { AtomicInteger atomicInteger = new AtomicInteger(1); //get()获取当前值。 int x = atomicInteger.get(); System.out.println("get()获取当前值 = " + x); //addAndGet(int delta)以原子方式将给定值与当前值相加 int i = atomicInteger.addAndGet(2); System.out.println("addAndGet(int delta)以原子方式将给定值与当前值相加 = " + i); // decrementAndGet()以原子方式将当前值减 1。 int i1 = atomicInteger.decrementAndGet(); System.out.println("decrementAndGet()以原子方式将当前值减 1 = " + i1); //doubleValue() 以 double 形式返回指定的数值。 double doubleValue = atomicInteger.doubleValue(); System.out.println("doubleValue() 以 double 形式返回指定的数值。 = " + doubleValue); //floatValue()以 float 形式返回指定的数值。 float floatValue = atomicInteger.floatValue(); System.out.println("floatValue()以 float 形式返回指定的数值。。 = " + floatValue); // intValue() 以 int 形式返回指定的数值。 int intValue = atomicInteger.intValue(); System.out.println("intValue() 以 int 形式返回指定的数值。= " + intValue); //etAndSet(int newValue)以原子方式设置为给定值,并返回旧值。 int andAdd = atomicInteger.getAndAdd(20); System.out.println("---------------------------------------------------------"); System.out.println("getAndAdd(int delta)以原子方式将给定值与当前值相加。旧值 = " + andAdd); System.out.println("新值 = " + atomicInteger.get()); System.out.println("---------------------------------------------------------"); //getAndDecrement()以原子方式将当前值加 1。 int andDecrement = atomicInteger.getAndDecrement(); System.out.println("getAndDecrement()以原子方式将当前值减 1。 = " + andDecrement); //getAndDecrement()以原子方式将当前值减 1。 int andIncrement = atomicInteger.getAndIncrement(); System.out.println("getAndDecrement()以原子方式将当前值减 1。" + andIncrement); //以原子方式将当前值加 1。 int incrementAndGet = atomicInteger.incrementAndGet(); System.out.println("以原子方式将当前值加 1。" + incrementAndGet); } }
结果:
get()获取当前值 = 1 addAndGet(int delta)以原子方式将给定值与当前值相加 = 3 decrementAndGet()以原子方式将当前值减 1 = 2 doubleValue() 以 double 形式返回指定的数值。 = 2.0 floatValue()以 float 形式返回指定的数值。。 = 2.0 intValue() 以 int 形式返回指定的数值。= 2 --------------------------------------------------------- getAndAdd(int delta)以原子方式将给定值与当前值相加。旧值 = 2 新值 = 22 --------------------------------------------------------- getAndDecrement()以原子方式将当前值减 1。 = 22 getAndDecrement()以原子方式将当前值减 1。21 以原子方式将当前值加 1。23
原子更新数组
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray<E>
/** * Created by soyoungboy on 2017/3/28. */ public class AtomicDemo { public static void main(String[] args) { testAtomicInteger(); } private static void testAtomicInteger() { Person person = new Person(11,"小强"); Person person1 = new Person(11,"大强"); Person[] peoples = new Person[]{person,person1}; AtomicReferenceArray<Person> personAtomicReferenceArray = new AtomicReferenceArray<Person>(peoples); printArray(personAtomicReferenceArray); // 以原子方式将位置 i 的元素设置为给定值,并返回旧值。 Person person2 = new Person(22, "老秦"); Person andSet = personAtomicReferenceArray.getAndSet(1, person2); System.out.println("返回的旧值 = "+andSet); printArray(personAtomicReferenceArray); //weakCompareAndSet(int i, E expect, E update) // 如果当前值 == 预期值,则以原子方式将位置 i 的元素设置为给定的更新值。 Person person3 = new Person(23, "哈哈"); //因为上面替换为老秦的person,所以如果是老秦的person,就替换 personAtomicReferenceArray.weakCompareAndSet(1,person2,person3); printArray(personAtomicReferenceArray); } private static void printArray(AtomicReferenceArray<Person> personAtomicReferenceArray) { System.out.println("-------------------------------------"); for (int i = 0;i<personAtomicReferenceArray.length();i++){ String s = personAtomicReferenceArray.get(i).toString(); System.out.println(s); } } }
结果:
------------------------------------- Person{age=11, name='小强'} Person{age=11, name='大强'} 返回的旧值 = Person{age=11, name='大强'} ------------------------------------- Person{age=11, name='小强'} Person{age=22, name='老秦'} ------------------------------------- Person{age=11, name='小强'} Person{age=23, name='哈哈'}
原子更新引用类型
- AtomicReference:原子更新引用类型。
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
- AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef,boolean initialMark)。
举例子:
import java.util.concurrent.atomic.AtomicReference; /** * Created by Administrator on 2017/3/28. */ public class AtomicReferenceDemo { public static void main(String[] args) { testAtomicReference(); } private static void testAtomicReference() { Person person1 = new Person(1,"姚明"); Person person2 = new Person(2,"易建联"); Person person3 = new Person(3,"王思聪"); AtomicReference<Person> personAtomicReference = new AtomicReference<>(person1); System.out.println("personAtomicReference"+personAtomicReference.get().toString()); System.out.println("------------------------------------------------------------"); //compareAndSet(V expect, V update)如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。 //person2肯定不是期望值,所以不会设置为person3,因此值应该为person1 personAtomicReference.compareAndSet(person2,person3); System.out.println("personAtomicReference"+personAtomicReference.get().toString()); System.out.println("------------------------------------------------------------"); // getAndSet(V newValue) 以原子方式设置为给定值,并返回旧值。 Person andSet = personAtomicReference.getAndSet(person2); System.out.println("旧值 = "+andSet); System.out.println("新值 = "+personAtomicReference.get().toString()); personAtomicReference.lazySet(person3); //lazySet(V newValue)最终设置为给定值。 System.out.println("lazySet "+personAtomicReference.get().toString()); } }
结果:
personAtomicReferencePerson{age=1, name='姚明'} ------------------------------------------------------------ personAtomicReferencePerson{age=1, name='姚明'} ------------------------------------------------------------ 旧值 = Person{age=1, name='姚明'} 新值 = Person{age=2, name='易建联'} lazySet Person{age=3, name='王思聪'}
原子更新字段类
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
- AtomicLongFieldUpdater:原子更新长整型字段的更新器。
- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。
newUpdater(Class<U> tclass, String fieldName)
使用给定字段为对象创建和返回一个更新器。
这是这个不同于上面类的方法,其他基本上一样。
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; /** * Created by Administrator on 2017/3/28. */ public class AtomicIntegerFieldUpdaterDemo { public static void main(String[] args) { testAtomicReference(); } private static void testAtomicReference() { Person person1 = new Person(1,"姚明"); AtomicReferenceFieldUpdater<Person, String> atomicReferenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater( Person.class, String.class, "name"); // 如果当前值 == 预期值,则以原子方式将此更新器管理的给定对象的字段设置为给定的更新值。 atomicReferenceFieldUpdater.compareAndSet(person1,person1.name,"哈哈"); System.out.println("person1 = "+person1.toString()); AtomicReferenceFieldUpdater<Person, Integer> atomicReferenceFieldUpdater1 = AtomicReferenceFieldUpdater.newUpdater( Person.class, Integer.class, "age"); // 如果当前值 == 预期值,则以原子方式将此更新器管理的给定对象的字段设置为给定的更新值。 atomicReferenceFieldUpdater1.compareAndSet(person1,person1.age,22); System.out.println("person1 = "+person1.toString()); } }
结果:
person1 = Person{age=1, name='哈哈'}
person1 = Person{age=22, name='哈哈'}