高并发的可见性搞不明白,就不用再研发了
旺道一键推广 -> 最新发布

本篇重点介绍

可见性是java中一种并不直观的特性,是指线程之间的可见性,即一个线程修改的状态对另一个线程是否是可见的,也就是一个线程修改了内存中的结果另一个线程能否马上就能看到。通常情况下,因为线程执行速度的快慢导致了线程间数据读取的先后问题,我们无法确保执行读操作的线程能适地看到其线程写入的值,有时甚至是根本不可能的事情。在高并发中可见性问题是保障线程之间交互的一个重要知识点。

禁止缓存的可见性变量

volatile是轻量级的同步机制

关键点:保证可见性、不保证原子性、禁止指令重排

保证可见性

解析:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。

当不添加volatile关键字时示例:

package com.jian8.juc;import java.util.concurrent.TimeUnit;/** * 1验证volatile的可见性 * 1.1 如果int num = 0,number变量没有添加volatile关键字修饰 * 1.2 添加了volatile,可以解决可见性 */public class VolatileDemo {    public static void main(String[] args) {        visibilityByVolatile();//验证volatile的可见性    }    /**     * volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改     */    public static void visibilityByVolatile() {        MyData myData = new MyData();        //第一个线程        new Thread(() -> {            System.out.println(Thread.currentThread().getName() + "\t come in");            try {                //线程暂停3s                TimeUnit.SECONDS.sleep(3);                myData.addToSixty();                System.out.println(Thread.currentThread().getName() + "\t update value:" + myData.num);            } catch (Exception e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }, "thread1").start();        //第二个线程是main线程        while (myData.num == 0) {            //如果myData的num一直为零,main线程一直在这里循环        }        System.out.println(Thread.currentThread().getName() + "\t mission is over, num value is " + myData.num);    }}class MyData {    //    int num = 0;    volatile int num = 0;    public void addToSixty() {        this.num = 60;    }}

输出结果:

thread1	 come inthread1	 update value:60//线程进入死循环

当我们加上volatile关键字后,volatile int num = 0;输出结果为:

thread1	 come inthread1	 update value:60main	 mission is over, num value is 60//程序没有死循环,结束执行

不保证原子性

描述:原子性是指数据整体在当前的业务中不可分割、具有完整性,数据在业务流转过程中必须保证完整,要么同时成功要么同时失败。

验证示例(变量添加volatile关键字,方法不添加synchronized):

package com.jian8.juc;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;/** * 1验证volatile的可见性 *  1.1 如果int num = 0,number变量没有添加volatile关键字修饰 * 1.2 添加了volatile,可以解决可见性 * * 2.验证volatile不保证原子性 *  2.1 原子性指的是什么 *      不可分割、完整性,即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么同时失败 */public class VolatileDemo {    public static void main(String[] args) {//        visibilityByVolatile();//验证volatile的可见性        atomicByVolatile();//验证volatile不保证原子性    }        /**     * volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改     */	//public static void visibilityByVolatile(){}        /**     * volatile不保证原子性     * 以及使用Atomic保证原子性     */    public static void atomicByVolatile(){        MyData myData = new MyData();        for(int i = 1; i <= 20; i++){            new Thread(() ->{                for(int j = 1; j <= 1000; j++){                    myData.addSelf();                    myData.atomicAddSelf();                }            },"Thread "+i).start();        }        //等待上面的线程都计算完成后,再用main线程取得最终结果值        try {            TimeUnit.SECONDS.sleep(4);        } catch (InterruptedException e) {            e.printStackTrace();        }        while (Thread.activeCount()>2){            Thread.yield();        }        System.out.println(Thread.currentThread().getName()+"\t finally num value is "+myData.num);        System.out.println(Thread.currentThread().getName()+"\t finally atomicnum value is "+myData.atomicInteger);    }}class MyData {    //    int num = 0;    volatile int num = 0;    public void addToSixty() {        this.num = 60;    }    public void addSelf(){        num++;    }        AtomicInteger atomicInteger = new AtomicInteger();    public void atomicAddSelf(){        atomicInteger.getAndIncrement();    }}

执行三次结果为:


//1.main	 finally num value is 19580	main	 finally atomicnum value is 20000//2.main	 finally num value is 19999main	 finally atomicnum value is 20000//3.main	 finally num value is 18375main	 finally atomicnum value is 20000//num并没有达到20000

禁止指令重排

描述:在计算机CPU中代码逻辑均是以一组组的指令形式交付给CPU。有序性是指在计算机执行程序时,为了提高性能,编译器在代码达到某些条件时会对代码逻辑的指令排序。

单线程环境里面每次执行代码只有一个线程,也就确保了程序最终执行结果和代码顺序执行的结果一致。(某些存在的竞态环境也会触发) 在多线程环境中由于线程交替执行,编译器优化重排的存在,两个线程中使用的变量能否保证一致性时无法确定的,导致了结果无法预测。


高并发的可见性搞不明白,就不用再研发了


代码实例:

描述:在公共类中声明成员变量:int a,b,x,y=0


高并发的可见性搞不明白,就不用再研发了

说明:常规情况下,线程按照正常的逻辑先后执行。在高并发下,如果编译器对这段程序代码执行重排优化后,可能出现如下情况:

高并发的可见性搞不明白,就不用再研发了

说明:在多线程环境下,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的。

volatile可以实现禁止指令重排,从而避免了多线程环境下程序出现乱序执行的现象

内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,他的作用有两个:

  • 保证特定操作的执行顺序
  • 保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)

由于编译器和处理器都能执行指令重排优化。如果在执行前Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排顺序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。(本质上由于高速缓存禁止了)


高并发的可见性搞不明白,就不用再研发了

JMM(java内存模型)

JMM(Java Memory Model)本身是一种抽象的概念,并不真实存在,他描述的一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式

JMM关于同步的规定

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

② 线程加锁前,必须读取主内存的最新值到自己的工作内存

③ 加锁解锁时同一把锁

解析:由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有的成为栈空间),工作内存是每个线程的私有数据区域,而java内存模型中规定所有变量都存储在JVM主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在私有内存区域中进行。每个线程在这个过程中都要将变量从主内存拷贝到自己的独占内存空间,在私内存中对变量副本进行操作,操作完成后再将变量副本写回主内存。由于不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存的变量副本,因此不同的线程件无法访问对方的工作内存,线程间的通信(传值)也必须通过主内存来完成。Voliate可以很好地保证共享变量在线程之间的可见性。

volatile的使用

当普通单例模式在多线程情况下:

public class SingletonDemo {    private static SingletonDemo instance = null;    private SingletonDemo() {        System.out.println(Thread.currentThread().getName() + "\t 构造方法SingletonDemo()");    }    public static SingletonDemo getInstance() {        if (instance == null) {            instance = new SingletonDemo();        }        return instance;    }    public static void main(String[] args) {        //构造方法只会被执行一次//        System.out.println(getInstance() == getInstance());//        System.out.println(getInstance() == getInstance());//        System.out.println(getInstance() == getInstance());        //并发多线程后,构造方法会在一些情况下执行多次        for (int i = 0; i < 10; i++) {            new Thread(() -> {                SingletonDemo.getInstance();            }, "Thread " + i).start();        }    }}

说明:其构造方法在多线程情况下可能会被执行多次

利用【volatile】的解决方式:

单例模式DCL代码

DCL (Double Check Lock双端检锁机制)在加锁前和加锁后都进行一次判断

public static SingletonDemo getInstance() {        if (instance == null) {            synchronized (SingletonDemo.class) {                if (instance == null) {                    instance = new SingletonDemo();                }            }        }        return instance;    }

大部分运行结果构造方法只会被执行一次,但指令重排机制会让程序很小的几率出现构造方法被执行多次。DCL(双端检锁)机制不一定能线程安全,由于线程执行速度快慢有别的原因或指令重排序,可能导致重入的风险。

描述在某一个线程执行到第一次检测,读取到instance不为null时,instance的引用对象可能没有完成初始化。instance=new SingleDemo();可以被分为一下三步(伪代码):

memory = allocate();//1.分配对象内存空间instance(memory);	//2.初始化对象instance = memory;	//3.设置instance执行刚分配的内存地址,此时instance!=null

指令重排是不会对有明显依赖关系的代码进行重排序的,我们的代码可能存在着逻辑上的先后关系,但JVM并无法智能识别。比如图中步骤2和步骤3在依赖上不存在关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变串行语义执行的一致性,在JVM看来这种重排优化是被允许的。如图如果3步骤提前于步骤2,而instance还没有初始化完成,这个时候便出现A线程访问instance不为null时,由于instance实例尚未初始化完成,线程A本该获取到存在的instance实例,但由于错误判断获取也就造成了线程安全问题。

单例模式volatile代码

说明:在上述代码中的SingletongDemo实例上加上【volatile】:

private static volatile SingletonDemo instance = null;

有任何问题欢迎留言交流~


整理总结不易,如果觉得这篇文章有意思的话,欢迎转发、收藏,给我一些鼓励~

有想看的内容或者建议,敬请留言!

最近利用空余时间整理了一些精选Java架构学习视频和大厂项目底层知识点,需要的同学欢迎私信我发给你~一起学习进步!有任何问题也欢迎交流~

Java日记本,每日存档超实用的技术干货学习笔记,每天陪你前进一点点~

教育培训机构-如何策划节日招生?
做教育培训,最重要的事情莫过于招生了。面对不同的学员,招生方…于招生。目标不同,活动策划也会不一样。② 活动主题要鲜明不仅主题要鲜明,主题之间要有连续性。就像看连续剧一样,一集接一集,一环扣一环。策划好一系列活动,一个接一个来,从多个角度延续活动的宣传,突出主题,引起关注度。常用的招生方式有:体验课招…-------------想要获得更多运营技巧、doc文案,
教育培训:如何提高满班率和续班率
什么是满班率?就是实际人数除以计划招生人数,这个值就是满班率…员有流失是正常的,但要保证一个概率。在保障老学员续期的基础上,我们也要大胆创新,开设网络课程,拓宽招生区域。实地课程录制后,进一步发行到商弈云课上,通过商弈云课系统转成真实流量,网上有人点击观看,点赞,可以按点播量分佣金。助你在日常的营销推…-----------------------想要获得更多运
培训机构难盈利的问题出在哪里,如何实现盈利?
竞争白热化实地培训班、托管辅导班竞争白热化了,利润越来越稀薄…现没有多少盈利。还有一部分负责人在刚招生时,掌握了不少资金,开始盲目扩张,盲目做课程推广,以期待获得更多生源和收费,从而使培训班陷入了这种恶性循环的陷阱里。其实这些到手的钱,并不是你的利润,而是你未来要支出的钱。如遇到行业快速发展时期,或者…-----想要获得更多运营技巧、doc文件,请“关注”并“转
“差异化”经营是网络培训赚钱的关键所在
“差异化经营”这个词,相信大多数人都耳熟能详,但要做到的却是…。在现有的基础上更新升级或者资源整合就是对现有的产品或者技术,进行升级版本,或者通过一些资源整合,成一个项目。你的技能以前是用来生产或制造产品,现在是把技能升级成知识培训,转换云课堂。直白一点就是可以让老师自行录好课程并上传到学校的商弈云课…在着缺点和不足之处。在创新改革时,要从功能、服务、价格等方面
教育培训行业是如何运作的?
以前读书时,学校附近有个小摊卖鸡蛋灌饼。刚开始的时候只有一家….%,剩下的一对一和多人大班其实市场份额都很小。家长报培训班,是希望孩子能够学到知识。老师通过课上教学和课后辅导来满足孩子的需求,达到成绩提高的目标。我见过上百人的大培训班,为什么一个班这么多人?因为是名师讲课,但人太多坐后面,基本听不到重…是你的核心竞争力。想要得到用户的认同,你就必须做到产品质量好
旺道:实体店怎么做网络营销?
年纪大了,体力不好了,便赚不了什么钱了。所以,一定要在年轻的…写文案都是非常头痛的事情,那么下面我会给大家提供一个解决方案。在互联网营销系统里面有很多细节营销,软文营销就是其中一种,但好多小白头痛写文章,所以可以用这个方法也一样可以植入广告语,或者是图片广告,轮显切换广告,使用 旺道营销系统,也就是每…以帮你吸引海量精准粉丝。有了粉丝,你通过广告、产品、服务就可
旺道:圈里圈外,铁饭碗距离瓷饭碗究竟有多远?
中国人爱混圈子,从线下一直混到线上,各种奇形怪状的圈子勾勒出…不能推则敷衍了事,盼着混到主任科员退休;一种是老黄牛式,踏踏实实、兢兢业业工作,但拙于人际关系,活干得最多,但依旧不见晋升迹象;还有一种是提线木偶式,你扯一下线他动一下,好像脑子和身体是分离的。我突然一拍脑袋,这是提前衰老的迹象啊,工作内容…即使选择留在铁饭碗圈里,也应保有随时离开的思想警惕和进行必要
旺道:微信要双开了,朋友圈推广更好做了
现在手机只能登一个微信,如两个,三个微信的小伙伴们就只能退出…动推广工具、 设置头像点击头像图标,点“+”选择图片,可以上传你的企业,个人头像,产品服务等。旺道微信朋友圈广告免费朋友圈广告-旺道、 设置推广主题点“广告”,再点“+”设置推广的主题(类别),填好中文主题,以及你的网址。教你不花一分钱做朋…简直就是一劳永逸哟。如果你点开发布的文章看不到广告,那就是你
旺道:如何能让网站多个关键词排名百度首页?
最近接了一个单,做婴儿游泳馆加盟的。优化推广范围是全国,但是…泳馆加盟哪家好”、“婴儿游泳加盟多少钱”这类与我息息相关的词。如何能让网站多个关键词排名百度首页?但客户并不认同,客户说这个词指数低,或者说搜的人不多。是,这是一方面原因。但是,指数低有什么关系,你是卖产品还是卖指数。搜的人不多有什么关系,…实现广告效果最大化,来满足大众化的广告需求。对于这样的四两拔
旺道:如何成为网络营销达人?这个技能帮你节省80%的时间
在某次名师讲坛上,听到了一个让人警醒的故事。说有个村子缺水,…准的产品。现在已经不是新鲜事物,好多企业因为百度竞价的高额费用,头疼不已,所以做网站关键词优化排名,成为了企业必选的推广项目。做优化也要做到有成效,不然就是一分钱也是昂贵的。用 旺道智能机器人 就可以持续代替人工去做,它非常聪明,只要你够耐…手,设备先进,你多努力奔跑也于事无补。简单说,要去一个地方,

此文章来源来互联网,如果你有任何版权冲突,可以联络我们,微信号:WANCOME,QQ:1444641。加我们时,请说明来意,我们将优先处理你的问题。




商弈云推  | 圈广告  | 跨圈推  | 圈推广  | 圈营销
粤ICP备10213132号
旺道商标注册证号:8608864
Copyright © 2016 环企网络信息科技有限公司 版权所有