4-1、4-2 线程安全性-原子性-atomic

转自 https://www.jianshu.com/p/895950290179

1、线程安全性

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协调,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

线程安全性主要体现:

  1. 原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作
  2. 可见性:一个线程对主内存的修改可以及时的被其他线程观察到
  3. 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般是杂乱无序

2、原子性

2.1 Atomic包

位于java.util.concurrent.atomic

AtomicXXX : CAS、Unsafe.compareAndSwapXXX

CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。
AtomicInteger

@Slf4j
@ThreadSafe
public class CountExample2 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;
    //从int类型换成了AtomicInteger
    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count.get());
    }

    private static void add() {
        //调用了AtomicInteger方法
        count.incrementAndGet();
        // count.getAndIncrement();
    }
}

线程安全?源码分析

AtomicInteger.java

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;
public final int incrementAndGet() {
        //this 代表当前AtomicInteger对象
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

Unsafe.java

//本地方法,java底层方法
/**
 * @param var1 对象
 * @param var2 偏移量
 * @param var3 期望值
 * @param var5 修改值
 */
 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

/**
 * @param var1 当前对象
 * @param var2 当前值
 * @param var3 增量
 */
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5; //底层值
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
        // CAS核心
        //当前值与底层值相同,则更新成var5 + var4
        //不一样则不停的循环,直到值相同
        return var5;
    }

AtomicInLong 与 LongAdder

@Slf4j
@ThreadSafe
public class AtomicExample2 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static AtomicLong count = new AtomicLong(0);

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count.get());
    }

    private static void add() {
        count.incrementAndGet();
        // count.getAndIncrement();
    }
}

@Slf4j
@ThreadSafe
public class AtomicExample3 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static LongAdder count = new LongAdder();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        count.increment();
    }
}

AtomicInLong 与 LongAdder 比较

就像我们所知道的那样,AtomicLong的原理是依靠底层的cas来保障原子性的更新数据,在要添加或者减少的时候,会使用死循环不断地cas到特定的值,从而达到更新数据的目的。如果竞争不激烈,修改成功几率很高,否则失败概率很高,在失败几率很高的情况下,这些原子操作就会进行多次的循环操作尝试,因此性能会受到影响。

对于普通类型的Long和Doubble变量,JVM允许将64位的读操作或写操作拆成两个三十二位的操作。

LongAdder的核心是将热点数据分离,比如说它可以将AtomicLong内部核心数据value分离成一个数组,每个线程访问时,通过hash等算法,映射到其中一个数字进行计数,最终的计数结果则会这个数据的求和累加,其中热点数据value会被分离成多个cell,每个cell独自维护内部的值,当前对象实际值为所有cell累计合成,这样的话,热点就进行了有效的分离,并提高了并行度。

LongAdderAtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并发的时候通过分散提高了性能。

​ 缺点是LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。

​ 实际使用中,在处理高并发时,可以优先使用LongAdder,而不是继续使用AtomicLong,当然,在线程竞争很低的情况下,使用AtomicLong更简单更实际一些,并且效率会高些。其他情况下,比如序列号生成,这种情况下需要准确的数值,全局唯一的AtomicLong才是正确的选择,而不是LongAdder

AtomicReference

The AtomicReference class provides reference objects that may be read and written atomically, so when multiple threads try to reach them at the same time, only one will be able to do so

原子性引用

@Slf4j
@ThreadSafe
public class AtomicExample4 {

    private static AtomicReference<Integer> count = new AtomicReference<>(0);

    public static void main(String[] args) {
        count.compareAndSet(0, 2); // 2
        count.compareAndSet(0, 1); // no
        count.compareAndSet(1, 3); // no
        count.compareAndSet(2, 4); // 4
        count.compareAndSet(3, 5); // no
        log.info("count:{}", count.get());
    }
}

以上实例比较简单,我有个疑问?假如我们引用的是一个自定义的对象,并且对象里面有属性值,然后,修改对象中的属性值也是原子性的吗?还是只是对对象的引用是原子性操作。

带着上面的疑问,进行源码分析

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); }
    }

    private volatile V value;

    /**
     * Creates a new AtomicReference with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicReference(V initialValue) {
        value = initialValue;
    }

    /**
     * Creates a new AtomicReference with null initial value.
     */
    public AtomicReference() {
    }

    /**
     * 不需要安全防护
     */
    public final V get() {
        return value;
    }

    /**
     * 设值值不需要进行对象安全防护
     */
    public final void set(V newValue) {
        value = newValue;
    }


    /**
     * 很明显调用的是csa操作
     * 比较对象是否相同,进行设值
     * 设值成功返回true,否则返回false
     */
    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);
    }
}

通过源码分析,可以得出

  • AtomicReference 所提供的某些方法可以进行原子性操作,如compareAndSet、getAndSet,这仅仅是对引用进行原子性操作

  • AtomicReference 不能保证对象中若存在属性值修改是线程安全的,如假设引用对象是person,修改person中name和age,多个线程同时从引用中获得对象,并进行修改,会出现线程不安全情况。下面我们通过代码来验证一下这条结论。

    @Slf4j
    @NotThreadSafe
    public class AtomicReferenceTest {
    
      // 请求总数
      public static int clientTotal = 1000;
    
      // 同时并发执行的线程数
      public static int threadTotal = 500;
    
      public static Person person = new Person(0,0);
    
    
      public static AtomicReference<Person> personAtomicReference = new AtomicReference(person);
    
      public static void main(String[] args) throws Exception {
          ExecutorService executorService = Executors.newCachedThreadPool();
          final Semaphore semaphore = new Semaphore(threadTotal);
          final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
          for (int i = 0; i < clientTotal ; i++) {
              final int result = i;
              executorService.execute(() -> {
                  try {
                      semaphore.acquire();
                      modify(result);
                      semaphore.release();
                  } catch (Exception e) {
                      log.error("exception", e);
                  }
                  countDownLatch.countDown();
              });
          }
          countDownLatch.await();
          executorService.shutdown();
          log.info("name:{},age:{}",personAtomicReference.get().getName(), personAtomicReference.get().getAge());
      }
    
      //如果线程安全的话,age的值和name的值是一致的
      //如果线程不安全的话,age的值和name是不一样的。
      private static void modify(int i) {
          personAtomicReference.get().setAge(personAtomicReference.get().getAge() + i);
          personAtomicReference.get().setName(personAtomicReference.get().getName() + i);
      }
    }
    

在低并发的情况下,输出的结果是正确的,但是在高并发的情况下结果差距就很大了

18:09:52.473 [main] INFO com.mmall.concurrency.example.atomic.AtomicReferenceTest - name:496592,age:496922

AtomicReferenceFieldUpdater

atomic包中提供AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater、AtomicLongFieldUpdater,原子性的更新某一个类实例的指定的某一个字段

AtomicIntegerFieldUpdater

@Slf4j
@ThreadSafe
public class AtomicExample5 {
    //AtomicIntegerFieldUpdater 原子性的更新某一个类的实例的指定的某一个字段
    //并且该字段由volatile进行修饰同时不能被static修饰
    //有些网上说而且不能被private修饰?下文将进行验证
    private static AtomicIntegerFieldUpdater<AtomicExample5> updater =
            AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");

    @Getter
    public volatile int count = 100;

    public static void main(String[] args) {

        AtomicExample5 example5 = new AtomicExample5();

        if (updater.compareAndSet(example5, 100, 120)) {
            log.info("update success 1, {}", example5.getCount());
        }

        if (updater.compareAndSet(example5, 100, 120)) {
            log.info("update success 2, {}", example5.getCount());
        } else {
            log.info("update failed, {}", example5.getCount());
        }
    }
}

结果输出

18:48:27.815 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample5 - update success 1, 120
18:48:27.825 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample5 - update failed, 120

源码分析

public abstract class AtomicIntegerFieldUpdater<T> {
    /**
     *
     * @param tclass 持有某字段的类
     * @param fieldName 字段名字
     */
    @CallerSensitive
    public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
                                                              String fieldName)     {
        return new AtomicIntegerFieldUpdaterImpl<U>
            (tclass, fieldName, Reflection.getCallerClass());
    }
    
    /**
     * 原子性设置
     */
    public int getAndSet(T obj, int newValue) {
        int prev;
        do {
            prev = get(obj);
        } while (!compareAndSet(obj, prev, newValue));
        return prev;
    }
    
    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;
            //用Unsafe里的那一坨方法去原子更新
            offset = unsafe.objectFieldOffset(field);
        }
    }
}

从源码分析中,可以看出没有要求不能被private修饰
AtomicStampReference

​ 此类是要核心解决CAS的ABA问题

​ ABA问题:指CAS操作的时候,线程将某个变量值由A修改为B,但是又改回了A,其他线程发现A并未改变,于是CAS将进行值交换操作,实际上该值已经被改变过,这与CAS的核心思想是不符合的

​ 解决思路:每次变量更新的时候,把变量的版本号进行更新,如果某变量被某个线程修改过,那么版本号一定会递增更新,从而解决ABA问题

​ J.U.C 提供了两个类解决ABA问题,一个是AtomicStampReference,另一个是AtomicMarkableReference

AtomicLongArray
AtomicLong是作用是对长整形进行原子操作。而AtomicLongArray的作用则是对"长整形数组"进行原子操作,根据索引,对数据中的指定位置的数据进行院子性的更新

AtomicBoolean

@Slf4j
@ThreadSafe
public class AtomicExample6 {

    private static AtomicBoolean isHappened = new AtomicBoolean(false);

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    test();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("isHappened:{}", isHappened.get());
    }

    /*
     * 演示如何让一段代码中的某个逻辑在高并发场景下只执行一次
     */
    private static void test() {
        //原子性操作,保证从false 到 true 只会执行一次
        if (isHappened.compareAndSet(false, true)) {
            log.info("execute"); //只会执行一次
        }
    }
}

作者:chuIllusions丶
链接:https://www.jianshu.com/p/895950290179
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

猜你喜欢

转载自blog.csdn.net/csdnlijingran/article/details/83003589