首页 > 技术文章 > JUC并发编程原子操作类

tjava 2020-09-19 10:49 原文

  

      大纲:

        一、原子操作类介绍

       二、原子性类型

  

      原子操作类介绍

      多线程资源的共享,需要为其增加同步锁,保证数据的结果正确性,但是过多的同步操作可能会造成死锁,导致程序进入停滞状态,且这样的问题很难排查。而且这样对性能也有影响。所以在这种情况下就引入了原子性的控制,来解决这样的问题。

      范例:没有提供同步操作观察问题

      此时加入有10个线程同时在运行,每一次运行让num的变量的值加一,那么最终的结果一定是10。但是此时返回的结果是9,原因是线程是随机执行的,哪一个线程先占用CPU的资源,哪一个线程就先执行,那么这里的情况就是多个线程同时去抢占一个资源时,可能一个线程还没有递增算法操作,而下一个线程就已经为其增加好了,这样就会导致结果不正确。这个问题在最原始的解决方案就是加入同步锁(synchronized)来解决,这个方案就是当有一个线程正在执行时,就不会让其他线程执行,必须等到正在执行的线程运行完毕之后,才执行下一个线程。

package cn.txp.juc.atom;
import java.util.concurrent.TimeUnit;
class Count{
    private int num = 0;
    public void addOne() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {}
        this.num ++;
    }
    public int getNum() {
        return this.num;
    }
}
public class JUCAtomDemo1 {
    public static void main(String[] args) throws Exception{
        Count count = new Count();
        for(int x = 0 ; x < 10 ; x ++) {
            new Thread(() -> {
                count.addOne();
            }).start();
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println("最终计算结果:" + count.getNum());   // 结果 = 9     多运行几次结果不一
    }
}

 

      范例:加入原始的同步锁解决

package cn.txp.juc.atom;
import java.util.concurrent.TimeUnit;
class Count{
    private int num = 0;
    public synchronized void addOne() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {}
        this.num = this.num + 1;
        System.out.println(this.num);
    }
    public synchronized int getNum() {
        return this.num;
    }
}
public class JUCAtomDemo1 {
    public static void main(String[] args) throws Exception{
        Count count = new Count();
        for(int x = 0 ; x < 10 ; x ++) {
            System.out.println(x);
            new Thread(() -> {
                count.addOne();
            }).start();
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println("最终计算结果:" + count.getNum());
    }
}

 

     

      范例:用原子类实现,此时避免了大量的使用synchronized(同步锁)和唤醒处理机制。

package cn.txp.juc.atom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class Count{
    private AtomicInteger num = new AtomicInteger();
    public void addOne() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {}
        this.num.addAndGet(1); // 加一
    }
    public AtomicInteger getNum() {
        return this.num;
    }
}
public class JUCAtomDemo1 {
    public static void main(String[] args) throws Exception{
        Count count = new Count();
        for(int x = 0 ; x < 10 ; x ++) {
            System.out.println(x);
            new Thread(() -> {
                count.addOne();
            }).start();
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println("最终计算结果:" + count.getNum());
    }
}

 

 

 

      原子性类型

      在JUC中,原子性类型分为了一下几种:

        1、基本原子类型

        2、原子的数组类型

        3、原子的引用类型

        4、原子的属性修改器

        5、原子计算器

       

    1、基本原子类型

      主要描述的是一些常规的数据操作,有AtomicBoolean, AtomicInteger, AtomicLong这三种对应类型,这几种对应的类型操作都非常的类似,上面的一个demo中就是使用AtomicInteger来进行操作的。    

      常用操作方法:

public AtomicLong() {} 设置保存的类型为0
public AtomicLong(long num) {} 设置指定的初始化类容
public final long addAndGet(long delta) {} 增加数据同时返回计算后的结果
public final long decrementAndGet() {} 自减并返回结果
public final long incrementAndGet() {} 自增并返回结果
public final void set(long newValue) {} 设置新的数据内容
public final long get() {} 获取原始的数据内容
public final boolean compareAndSet(long expectedValue, long newValue) {} CAS操作,进行数据的安全操作修改

 

 

 

 

 

 

 

 

 

 

 

     例:

package cn.txp.juc.atom;
import java.util.concurrent.atomic.AtomicLong;
public class JUCAtomDemo2 {
    public static void main(String[] args) throws Exception{
        AtomicLong al = new AtomicLong(10);
        System.out.println("增加并返回结果:" + al.addAndGet(10));
        al.set(99);
        System.out.println(al.get());
    }
}

 

 

      范例:compareAndSet()方法是采用CAS定义的,CAS是乐观锁的处理机制,CAS进行操作的时候并不是利用Java虚拟机来实现的,而是直接利用底层的交互类来实现的,也就是说CAS是利用最底层的代码实现的。CAS的操作就是将当前的内容与预期的内容进行判断,如果相同则进行内容的交换。

package cn.txp.juc.atom;
import java.util.concurrent.atomic.AtomicLong;
public class JUCAtomDemo3 {
    public static void main(String[] args) throws Exception{
        AtomicLong al = new AtomicLong(10);
        System.out.println(al.compareAndSet(20, 70));
        System.out.println(al.get());
    }
}

 

     2、原子数组类型

      原子数组类型有如下几个类:AtomicIntegerArray<整型数组>、AtomicLongArray<长整型数组>、AtomicReferenceArray<引用数组>。    

      AtomicReferenceArray为例:

方法 描述
public AtomicReferenceArray(int length) 定义要操作数组的空白长度
public AtomicReferenceArray(E[] array) 设置直接要操作的数组
public final boolean compareAndSet(int i , E expectedValue, E newValue) 进行数组的比较替换操作
public final E get(int i) 获取指定索引的数组
public final void set(int i , E newValue) 设置指定索引的数组

 

      

 

 

 

 

 

 

 

       范例:

package cn.txp.juc.atom.array;
import java.util.concurrent.atomic.AtomicReferenceArray;
public class JucAtomicReferenceArrayDemo {
    public static void main(String[] args) {
        AtomicReferenceArray<String> array = new AtomicReferenceArray<String>(5);
        array.set(0, "www.baidu.com");
        array.set(1, "test");
        array.set(2, "array");
        System.out.println(array.compareAndSet(1, "test", "dd"));
        System.out.println(array.get(1));
    }
}

 

 

       3、原子的引用类型  

          引用原子操作类有几个常用类:AtomicReference 引用类型原子类、AtomicMarkableReference 标记节点原子引用类、AtomicStampedReference  引用版本号原子类

方法 描述
public AtomicReference() 设置一个空引用的数据内容
public AtomicReference(V initValue) 设置要引用的数据内容
public final boolean compareAndSet(V expectedValue, V newValue) CAS比较替换操作
public final V get() 获取设置的内容
public final V set(V newValue) 设置要操作的数据内容

 

 

 

 

 

 

 

 

    范例:AtomicReference

package cn.txp.juc.atom.reference;
impor java.util.concurrent.atomic.AtomicReference;
class Person{
    private String name;
    private Integer age;
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "name: " + this.name + "\t" + "age: " + this.age;
    }
}
public class JucRefrenceDemo {
    public static void main(String[] args) {
        Person per1 = new Person("A1", 10);
        Person per2 = new Person("C", 10);
        AtomicReference<Person> atomicRe = new AtomicReference<>(per1);
        System.out.println(atomicRe.compareAndSet(per1, per2));
        System.out.println(atomicRe.get());
    }
}

 

      范例:AtomicStampeReference,根据版本号实现数据的修改

方法 描述
public AtomicStampedReference(V initref, int initialStamp) 置一个初始化引用和初始化数值标记
public V getReference() 获取当前引用
public int getStamp() 获取当前的标记
public boolean compareAndSet(V   expectedReference, V   newReference, int expectedStamp, int newStamp) CAS比较替换操作
public void set(V newReference, int newStamp) 设置新的内容并指定新的标记

 

 

 

 

 

 

 

 

      范例:AtomicStampedReference

package cn.txp.juc.atom.reference;
import java.util.concurrent.atomic.AtomicStampedReference;
class Member{
    private String name;
    private Integer age;
    public Member(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "name: " + this.name + "\t" + "age: " + this.age;
    }
}
public class JucRefrenceDemo2 {
    public static void main(String[] args) {
        Member mem1 = new Member("testMember", 10);
        Member mem2 = new Member("testMem", 20);
        AtomicStampedReference<Member> atomicMember = new AtomicStampedReference<Member>(mem1, 1);
        atomicMember.compareAndSet(mem2, mem2, 1, 3);
        System.out.println(atomicMember.getReference());
        System.out.println(atomicMember.getStamp());
    }
}

 

 

      范例:AtomicMarkableReference类,在使用AtomicStampedReference类的时候,需要每一次给一个版本作为标识,这样在开发中过于麻烦,AtomicMarkableReference就属于它的简化操作。

方法 描述
public AtomicMarkableReference(V initRefe, boolean initMarkable) 初始化内容并设置一个标记
public boolean isMarked() 获取当前的标记
public V getReference() 获取当前引用数据
public void set(V newReference, boolean newMark) 设置引用数据并设置标记
public boolean compareAndSet(V   expectedReference, V   newReference, boolean expectedMark, boolean newMark) 比较替换内容

 

 

 

 

 

 

 

 

      范例:AtomicMarkableReference

package cn.txp.juc.atom.reference;
import java.util.concurrent.atomic.AtomicMarkableReference;
class News{
    private String title;
    private String content;
    public News(String title, String content) {
        this.title = title;
        this.content = content;
    }
    @Override
    public String toString() {
        return "title: " + this.title + "\t" + "content: " + this.content;
    }
}
public class JucRefrenceDemo3 {
    public static void main(String[] args) {
        News news = new News("title1", "内容");
        News newss = new News("titd1", "内容3");
        AtomicMarkableReference<News> mark = new AtomicMarkableReference<News>(news, true);
        mark.compareAndSet(news, newss, true, false);
        System.out.println(mark.isMarked());
        System.out.println(mark.getReference());
        System.out.println(mark.isMarked());
    }
}

 

      4、原子的属性修改器

        原子的引用类型是将对象放到原子类中进行保护,从而实现同步的处理,但是这样的操作无法同步类中的属性,如果要同步属性,可以通过原子的属性修改器来完成。

         原子的属性修改器有几个常用类:AtomicIntegerFieldUpdater(针对于Integer类型进行保护)、AtomicLongFieldUpdater(针对于Long类型进行保护)、AtomicReferenceFieldUpdater(针对于引用进行保护

方法 描述
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName) 获取属性修改器实例对象
public abstract boolean compareAndSet(T obj, int expect, int update) 比较替换操作。

 

 

 

 

        范例:AtomicIntegerFieldUpdater

package cn.txp.juc.atom.field;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
class Member{
    private String name;
    private volatile int age;
    public Member(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Member.class, "age");
        fieldUpdater.compareAndSet(this, this.age, age);
    }
    @Override
    public String toString() {
        return "name:" + this.name + "age" + this.age;
    }
}
public class IntegerFieldUpdater {
    public static void main(String[] args) {
        Member mem = new Member("名称", 20);
        mem.setAge(15);
        System.out.println(mem);
    }
}

 

     

      5、原子计算器

       并发计算操作中,提供累加器(LongAccumulator、DoubleAccumulator)和加法器(LongAdder、DoubleAdder)的概念。

       范例:使用累加器进行计算

package cn.txp.juc.atom.adder;
import java.util.concurrent.atomic.DoubleAccumulator;
public class AdderDemo1 {
    public static void main(String[] args) {
        DoubleAccumulator doubleAccumulator = new DoubleAccumulator((x, y) -> x + y, 1.1);
        doubleAccumulator.accumulate(20);
        System.out.println(doubleAccumulator.get());
    }
}

 

package cn.txp.juc.atom.adder;

import java.util.concurrent.atomic.DoubleAdder;

public class AdderDemo2 {
    public static void main(String[] args) {
        DoubleAdder adder = new DoubleAdder();
        adder.add(10);
        adder.add(20);
        System.out.println(adder);
    }
}

 

 

    知乎文章: https://www.zhihu.com/people/tan-xu-peng-44


    编程专业知识:https://www.cnblogs.com/tjava


    编程开发技术问题解决CSDN:https://blog.csdn.net/qq_37291829

      

      

 

推荐阅读