资讯专栏INFORMATION COLUMN

线程安全性-原子性

mtunique / 858人阅读

摘要:线程安全性原子性提供了互斥访问,同一时刻只能有一个线程来对他进行操作。原子性包是通过来保证线程原子性通过比较操作的对象的值工作内存的值与底层的值共享内存中的值对比是否相同来判断是否进行处理,如果不相同则重新获取。

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

原子性:提供了互斥访问,同一时刻只能有一个线程来对他进行操作。

可见性:一个线程对主内存的修改可以及时被其他线程观察到。

有序性:一个线程观察其他线程中的指令顺序,由于指令重排序的存在,该结果一般杂乱无序。

原子性 - Atomic包

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

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

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

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

AtomicReference、AtomicReferenceFieldUpdater
AtomicReference是给定指定的期望值当期望值与主内存中的值相同然后更新,示例代码

@Slf4j
public class AtomicExample4 {

    private static AtomicReference 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 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包还可以使用锁来实现。

synchronize:依赖jvm

修饰代码块:适用范围大括号括起来的代码,作用于调用的对象

修饰方法:适用范围整个方法,作用于调用的对象

修饰静态方法:适用范围整个静态方法,作用于所有对象

修饰一个类:适用范围是括起来的部分,作用于所有对象

Lock:依赖特殊的cpu指令、代码实现,ReentrantLock

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74433.html

相关文章

  • 线程基础必要知识点!看了学习多线程事半功倍

    摘要:是需要我们去处理很多事情,为了防止多线程给我们带来的安全和性能的问题下面就来简单总结一下我们需要哪些知识点来解决多线程遇到的问题。 前言 不小心就鸽了几天没有更新了,这个星期回家咯。在学校的日子要努力一点才行! 只有光头才能变强 回顾前面: 多线程三分钟就可以入个门了! Thread源码剖析 本文章的知识主要参考《Java并发编程实战》这本书的前4章,这本书的前4章都是讲解并发的基...

    YPHP 评论0 收藏0
  • Java并发编程之原子操作

    摘要:将与当前线程建立一对一关系的值移除。为了让方法里的操作具有原子性,也就是在一个线程执行这一系列操作的同时禁止其他线程执行这些操作,提出了锁的概念。 上头一直在说以线程为基础的并发编程的好处了,什么提高处理器利用率啦,简化编程模型啦。但是砖家们还是认为并发编程是程序开发中最不可捉摸、最诡异、最扯犊子、最麻烦、最恶心、最心烦、最容易出错、最不符合社会主义核心价值观的一个部分~ 造成这么多最...

    instein 评论0 收藏0
  • 【Java并发】线程安全

    摘要:另一个是使用锁的机制来处理线程之间的原子性。依赖于去实现锁,因此在这个关键字作用对象的作用范围内,都是同一时刻只能有一个线程对其进行操作的。 线程安全性 定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。 线程安全性主要体现在三个方面:原子性、可见性...

    刘玉平 评论0 收藏0
  • Java - 并发 atomic, synchronization and volatile

    摘要:线程的这种交叉操作会导致线程不安全。原子操作是在多线程环境下避免数据不一致必须的手段。如果声明一个域为一些情况就可以确保多线程访问到的变量是最新的。并发要求一个线程对对象进行了操作,对象发生了变化,这种变化应该对其他线程是可见的。 虽是读书笔记,但是如转载请注明出处 http://segmentfault.com/blog/exploring/ .. 拒绝伸手复制党 一个问题: ...

    StonePanda 评论0 收藏0
  • 并发编程安全问题:可见原子和有序

    摘要:线程切换带来的原子性问题我们把一个或者多个操作在执行的过程中不被中断的特性称为原子性。编译优化带来的有序性问题顾名思义,有序性指的是程序按照代码的先后顺序执行。 缓存导致的可见性问题 一个线程对共享变量的修改,另外一个线程能够立刻看到,称为可见性 在多核下,多个线程同时修改一个共享变量时,如++操作,每个线程操作的CPU缓存写入内存的时机是不确定的。除非你调用CPU相关指令强刷。 sh...

    pcChao 评论0 收藏0

发表评论

0条评论

mtunique

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<