线程安全性-原子性

线程安全性

定义

当多个线程访问同一个类时,无论运行时环境采用何种调度方式,不论线程如何交替执行,在主调代码中不须要额外的协同或者同步代码时,这个类均可以表现出正确的行为,咱们则称这个类为线程安全的。

线程安全性

  1. 原子性:提供了互斥访问,同一时刻只能有一个线程来对他进行操做。
  2. 可见性:一个线程对主内存的修改能够及时被其余线程观察到。
  3. 有序性:一个线程观察其余线程中的指令顺序,因为指令重排序的存在,该结果通常杂乱无序。

原子性 - Atomic包

  1. AtomicXXX 是经过 CAS(CompareAndSwap)来保证线程原子性 经过比较操做的对象的值(工做内存的值)与底层的值(共享内存中的值)对比是否相同来判断是否进行处理,若是不相同则从新获取。如此循环操做,直至获取到指望的值。

(关于什么是主内存什么事工做内存在上篇博客中进行介绍了,不懂的同窗能够翻一下)示例代码:java

@Slf4j
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();
    }
}
  1. LongAdder和DoubleAdder

jdk8中新增的保证同步操做的类,咱们以前介绍了AtomicXXX来保证原子性,那么为何还有有LongAdder呢?
说AtomicXXX的实现是经过死循环来判断值的,在低并发的状况下AtomicXXX进行更改值的命中率仍是很高的。可是在高并发下进行命中率可能没有那么高,从而一直执行循环操做,此时存在必定的性能消耗,在jvm中咱们容许将64位的数值拆分红2个32位的数进行储存的,LongAdder的思想就是将热点数据分离,将AtomicXXX中的核心数据分离,热点数据会被分离成多个数组,每一个数据都单独维护各自的值,将单点的并行压力发散到了各个节点,这样就提升了并行,在低并发的时候性能基本和AtomicXXX相同,在高并发时具备较好的性能,缺点是在并发更新时统计时可能会出现偏差。在低并发,须要全局惟一,准确的好比id等使用AtomicXXX,要求性能使用LongAdder数组

@Slf4j
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);、】【poiuytrewq;'
        
        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();
    }
}
  1. AtomicReference、AtomicReferenceFieldUpdater
    AtomicReference是给定指定的指望值当指望值与主内存中的值相同而后更新,示例代码
@Slf4j
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());
    }
}
AtomMNBVCXZenceFieldUpdater主要是更新某一个实例对象的一个字段这个字段必须是用volatile修饰同时不能是private修饰的,·157-=·   123444457890-
@Slf4j
public class AtomicExample5 {

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

最后咱们介绍一下使用AtomicBoolean来实现只执行一次的操做,咱们使用private static AtomicBoolean isHappened = new AtomicBoolean(false)来初始化一个具备原子性的一个Boolean的记录是否已经被执行安全

@Slf4j
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() {
        if (isHappened.compareAndSet(false, true)) {
            log.info("execute");
        }
    }
}

原子性 - 锁

咱们除了可使用Atomic包还可使用锁来实现。
  1. synchronize:依赖jvm
  • 修饰代码块:适用范围大括号括起来的代码,做用于调用的对象
  • 修饰方法:适用范围整个方法,做用于调用的对象
  • 修饰静态方法:适用范围整个静态方法,做用于全部对象
  • 修饰一个类:适用范围是括起来的部分,做用于全部对象
  1. Lock:依赖特殊的cpu指令、代码实现,ReentrantLock
相关文章
相关标签/搜索