1.什么是原子类
(1)原子类简介
- 一度认为原子是不可分割的最小单元,故原子类可以认为其操作都是不可分割的。
(2)为什么要有原子类
对多线程访问一个变量,我们需要加锁,而锁是比较消耗性能的,JDK1.5之后,新增的原子操作类提供了一种用法简单、性能高效、线程安全的更新一个变量的方式,这些同类位于JUC包下的atomic包下,发展到JDK1.8,该包下共有17个类,囊括了原子更新基本类型、原子更新属性、原子更新引用。
(3)JDK1.8新增的原子类
- DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder、Striped64
2.原子更新基本类型
- JDK1.8基本类型原子类有以下分类
- AtomicBoolean:原子更新布尔类型
- AtomicInteger:原子更新整型
- AtomicLong:原子更新长整型
- DoubleAdder:对Double的原子更新性能进行优化提升
- LongAdder:对Long的原子更新性能进行优化提升
- DoubleAccumulator:支持自定义运算
- LongAccumulator:支持自定义运算
- AtomicInteger的常见用法
- AtomicInteger(int initialValue):创建一个AtomicInteger实例,初始值由参数指定。不带参的构造方法初始值为0。
- int addAndGet(int delta) :以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果,与getAndAdd(int delta)相区分,从字面意思即可区分,前者返回相加后结果,后者先返回再相加。
- boolean compareAndSet(int expect, int update) :如果当前值等于预期值,则以原子方式将该值设置为输入的值。
- int getAndIncrement():以原子方式将当前值加1,注意:这里返回的是自增前的值。
- void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
- int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。
- 案例实战
- 线程不安全的例子
public class Demo1 { private static Integer num = 0; void addUnSafe(){ //对atomicInteger进行++操作 num++; } public static void main(String[] args) throws InterruptedException { Demo1 demo1 = new Demo1(); new Thread(()->{ for (int i = 0; i < 10; i++) { for (int j = 0; j < 1000; j++) { demo1.addUnSafe(); } } }).start(); new Thread(()->{ for (int i = 0; i < 10; i++) { for (int j = 0; j < 1000; j++) { demo1.addUnSafe(); } } }).start(); Thread.sleep(2000L); System.out.println("num最后的值:"+num); }
- AtomicInteger多线程调用
public class Demo1 { private static AtomicInteger atomicInteger = new AtomicInteger(0); void addSafe(){ //对atomicInteger进行++操作 atomicInteger.incrementAndGet(); } public static void main(String[] args) throws InterruptedException { new Thread(()->{ for (int i = 0; i < 10; i++) { for (int j = 0; j < 1000; j++) { demo1.addSafe(); } } }).start(); new Thread(()->{ for (int i = 0; i < 10; i++) { for (int j = 0; j < 1000; j++) { demo1.addSafe(); } } }).start(); Thread.sleep(2000L); System.out.println("atomicInteger最后的值:"+atomicInteger.get()); } }
- LongAccumulator的简单使用
public class Demo2 { private static LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x*y,3L); public static void main(String[] args) { longAccumulator.accumulate(2); System.out.println(longAccumulator.get()); } }
3.原子更新数组类型
- AtomicIntegerArray:对Integer数组类型的操作
- AtomicLongArray:对Long数组类型的操作
- AtomicReferenceArray:对对象数组类型的操作
- AtomicIntegerArray的简单使用
public class Demo1 { public static void main(String[] args) { int[] arrInt = new int[]{2,3,4}; AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(arrInt); //给数组下标是1的元素+2 int i = atomicIntegerArray.addAndGet(1, 2); System.out.println(i); //支持自定义规则的操作 int i1 = atomicIntegerArray.accumulateAndGet(1, 2, (x, y) -> x > y ? x : y); System.out.println(i1); } }
4.原子地更新属性
(1)原子的更新某个类的某个字段时,就需要使用原子更新字段类,Atomic包提供了一下4个类进行原子字段更新。
- AtomicIntegerFieldUpdater:原子类int类型更改操作
- AtomicLongFieldUpdater:原子类long类型更改操作
- AtomicStampedReference:原子类引用类型更改操作,避免ABA问题,携带版本号
- AtomicMarkableReference:原子类引用类型更改操作,避免ABA问题,携带boolean值
- AtomicReferenceFieldUpdater:原子类引用类型更改操作
(2)AtomicReferenceFieldUpdater、AtomicLongFieldUpdater案例实战
public class Demo2 { public static void main(String[] args) { Student student = new Student(1L,"李祥"); //对long类型的数值改变 AtomicLongFieldUpdater<Student> atomicLongFieldUpdater = AtomicLongFieldUpdater.newUpdater(Student.class,"id"); //将Student的id改成10 atomicLongFieldUpdater.compareAndSet(student,1L,10L); //输出student的id System.out.println("更改student的id:"+student.id); //对引用类型的改变 AtomicReferenceFieldUpdater<Student, String> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name"); referenceFieldUpdater.compareAndSet(student,student.getName(),"李祥更改"); System.out.println("更改student的name:"+student.name); } }
public class Student { volatile long id; volatile String name; public Student(long id, String name) { this.id = id; this.name = name; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
使用原子更新类时注意的问题:
- 字段必须是volatile类型的,在线程之间共享变量时保证立即可见。
- 字段的描述类型是调用者与操作对象字段的关系一致,也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。
- 只能是实例变量,不能是类变量,也就是说不能加static关键字。
- 不能被final变量修饰的。
- 对于AtomicIntegerFiledUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其他包装类型(Integer\Long)。
- 修改包装类型就需要使用AtomicRefebceFieldUpdater。
(3)AtomicStampedReference使用
- AtomciStampedReference是一个带有时间戳的对象引用,能够很好的解决CAS机制中的ABA问题。
- 什么是ABA问题
- ABA场景案例
public class ABADemo { private static AtomicInteger index = new AtomicInteger(10); public static void main(String[] args) { new Thread(()->{ index.compareAndSet(10,11); index.compareAndSet(11,10); System.out.println(Thread.currentThread().getName()+": 进行了 10->11->10"); },"张三").start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(2); boolean flag = index.compareAndSet(10, 12); System.out.println(Thread.currentThread().getName()+":修改index结果:"+flag+",设置的新值是:"+index); } catch (InterruptedException e) { e.printStackTrace(); } },"李祥").start(); } }
- AtomciStampedReference案例
- compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)。
- 第一个参数expectedReference:表示预期值。
- 第二个参数newReference:表示要更新的值。
- 第三个参数expectedStamp:表示预期的时间戳。
- 第四个参数newStamp:表示要更新的时间戳。
public class AtomicStampedReferenceDemo { private static AtomicInteger index = new AtomicInteger(10); private static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference(10,1); public static void main(String[] args) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+":当前版本号为:"+stampedReference.getStamp()); stampedReference.compareAndSet(10,11,stampedReference.getStamp(),stampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+":当前版本号为:"+stampedReference.getStamp()); stampedReference.compareAndSet(11,10,stampedReference.getStamp(),stampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+":当前版本号为:"+stampedReference.getStamp()); },"张三").start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName()+":拿到的版本号为:"+stampedReference.getStamp()); boolean flag = stampedReference.compareAndSet(10, 12, stampedReference.getStamp(), stampedReference.getStamp() + 1); System.out.println(Thread.currentThread().getName()+":修改index结果:"+flag+",设置的新值是:"+stampedReference.getReference()); } catch (InterruptedException e) { e.printStackTrace(); } },"李祥").start(); } }
其实除了AtomicStampedReference类,还有一个原子类也可以解决,就是AtomicMarkableReference,它不是维护一个版本号,而是维护一个boolean类型的标记,用法没有AtomicStampedReference灵活。因此也只是在特定的场景下使用。