资讯专栏INFORMATION COLUMN

线程安全-可见性

call_me_R / 376人阅读

摘要:下图是读操作示意图操作都是指令级别的下面看一段演示代码请求总数同时并发执行的线程数我们多次运行个这段代码,发现结果并不是我们预期,只能保证可见性并不能保证原子性。

共享变量在线程间不可见的原因

线程的交叉执行

重排序结合线程交叉执行

共享变量更新后的值没有在工作内存与主内存间及时更新

使用synchronized的来保证可见性

使用synchronized的两条规定:

线程解锁前,必须把共享变量的最新值刷新到主内存

线程加锁锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意加锁与解锁是同一把锁)

volatile 来实现可见性
通过加入内存屏障和禁止重拍讯优化来实现可见性。

对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存

对volatile变量进行读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。

也就是说使用volatile关键字在读和写操作时都会强迫从主内存中获取变量值。

下图是使用volatile写操作的示意图

使用volatile写操作前会插入一条StoreStore指令来禁止在volatile写之前的普通写对volatile写的指令重排序优化,在写之后会插入一条StoreLoad屏障指令来防止上面的volatile写操作和下面可能有的读或者写进行指令重排序。

下图是volatile读操作示意图

volatile操作都是cpu指令级别的

下面看一段演示代码

@Slf4j
public class CountExample4 {

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

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

    public static volatile int count = 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);
    }

    private static void add() {
        count++;
        // 1、count
        // 2、+1
        // 3、count
    }
}

我们多次运行个这段代码,发现结果并不是我们预期5000,volatile只能保证可见性并不能保证原子性。

通常来说使用volatile需要具备两个条件

对变量写操作不依赖当前值

该变量没有包含在其他变量的所在的式中

所以volatile非常适合用作状态标记量,比如做为线程是否被初始化。还有就是用double check 我之前的博客就提到的单例模式中就使用了volatile来做double check 双重检查实现单例。

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

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

相关文章

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

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

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

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

    pcChao 评论0 收藏0
  • Java并发编程笔记(二)

    摘要:本文探讨并发中的其它问题线程安全可见性活跃性等等。当闭锁到达结束状态时,门打开并允许所有线程通过。在从返回时被叫醒时,线程被放入锁池,与其他线程竞争重新获得锁。 本文探讨Java并发中的其它问题:线程安全、可见性、活跃性等等。 在行文之前,我想先推荐以下两份资料,质量很高:极客学院-Java并发编程读书笔记-《Java并发编程实战》 线程安全 《Java并发编程实战》中提到了太多的术语...

    NickZhou 评论0 收藏0
  • 「Java并发编程实战」之对象的共享

    摘要:当某个不应该发布的对象被发布时,这种情况被称为逸出。线程安全共享线程安全的对象在其内部实现同步,因此多线程可以通过对象的公有接口来进行访问而不需要进一步的同步。 前言   本系列博客是对《Java并发编程实战》的一点总结,本篇主要讲解以下几个内容,内容会比较枯燥。可能大家看标题不能能直观的感受出到底什么意思,这就是专业术语,哈哈,解释下,术语(terminology)是在特定学科领域用...

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

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

    刘玉平 评论0 收藏0

发表评论

0条评论

call_me_R

|高级讲师

TA的文章

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