volatile关键字的一个解释

volatile的意思是不稳定的,也就是敏感的。

 

当使用volatile关键字修饰变量时,意味着任何对此变量的操作都会在内存中进行,不会产生副本,以保证共享变量的可见性,局部阻止了指令重排的发生。

每个线程都有独占的内存区域,如操作栈、本地变量表等等。线程本地内存保存了引用变量在堆内存中的副本,线程对变量的所有操作都在本地内存区域中进行,执行结束后再同步到堆内存中去。这里必然有一个时间差,在这个时间差内,该线程对副本的操作,对于其他线程都是不可见的。

 

由此可知,在使用单例设计模式时,即使用了双检锁也不一定会拿到最新的数据。

 

如下示例代码在高并发场景中会存在问题:

class LazyinitDemo {

    private static TransactionService service  = null;

    public static TransactionService getTransactionService() {

        if (service == null) {

            synchronized (this) {

                if (service == null) {

                    service = new TransactionService();

                }

            }

        }

        return service;

    }

}

 

 

下面进行分析:使用者在调用getTransactionService()时,有可能会得到初始化未完成的对象。这与java虚拟机的编译优化有关。对Java编译器而言,初始化TransactionService实例和将对象地址写到service字段并发原子操作,而且这两个阶段的执行顺序也是未定义的。假设某个线程执行new TransactionService()时,构造方法还未被调用,编译器仅仅为该对象分配了内存空间并设为默认值,此时若另一个线程调用getTransactionService()方法,由于service!=null,但此时service对象还没有被赋予真正有效的值,从而无法取道正确的service单例对象。这就是著名的双重检查锁定问题,对象引用在没有同步的情况下进行读操作,导致用户可能会获得未构造完成的对象。对于此问题,一种较为简单的解决方案就是用volatile关键字修饰目标属性,这样service就限制了编译器对它的相关读写操作以及禁止对它的读写操作进行指令重排,确定对象实例化之后才返回引用。

 

 

但是volatile解决的是多线程共享变量的可见性问题,类似于synchronized,但不具备synchronized的互斥性。所以对volatile变量的操作并非都具有原子性,这是一个很容易弄混的地方。

 

比如这个例子,一个线程对共享变量进行了10000次的i++操作,另一个线程进行10000

次i--操作,代码:

 

public class VolatileNotAtomic {

    private static volatile long count = 0L;

    private static final int NUMBER = 10000;



    public static void main(String[] args) {

        Thread subtractThread = new SubtractThread();

        subtractThread.start();


        for (int i = 0; i < NUMBER; i++) {

            count++;

        }


        //等待减法线程结束

        while (subtractThread.isAlive()) {}


        System.out.print("count最后的值为:" + count);


    }



    private static class SubtractThread extends Thread {

        public void run() {

            for (int i = 0; i < NUMBER; i++) {

                count--;

            }

        }

    }

}

 

执行多次发现基本都不为0。如果在count++和count--两处都进行加锁才会得到0的预期。

 

for (int i = 0; i < MAX_VALUE; i++) {

    synchronized (VolatileNotAtomic.class) {

        // 在count--代码处也同样进行加锁处理

        count++;

    }

}

 

 

因为count++的操作其实在计算机中进行了四步:

 

  1. 读取count并压入操作栈顶

  2. 常量1压入操作栈顶

  3. 取出最顶部两个元素进行相加

  4. 将刚才得到的和赋值给count

 

 

因此,“volatile是轻量级的同步方式”这种说法是错误的。它只是轻量级的线程操作可见方式,并非同步方式,如果是多写场景一定会产生线程安全问题。如果是一写多读的并发场景,使用volatile修饰变量则非常合适。volatile一写多读的最典型的应用是CopyOnWriteArrayList。它在修改数据时会把整个集合的数据全部复制出来,对写操作加锁,修改完成后,再用setArray()把array指向新的集合。使用volatile可以使读线程尽快的感知array的修改,不进行指令重排,操作后即对其他线程可见。源码如下:

public class CopyOnWriteArrayList<E> {

    // 集合真正存储元素的数组

    private transient volatile Object[] array;

    final void setArray(Object[] a) {

        array = a;

    }

}

 

 

在实际业务中,如何清晰的判断一写多读的场景显得尤为重要。如果不确定共享变量是否会被多个线程并发写,保险的做法是使用同步代码块来实现线程同步。另外,因为所有的操作都需要同步给内存变量,所以volatile一定会使线程的执行速度变慢,故要审慎定义和使用volatile属性。

 

 

源码地址: https://pan.quark.cn/s/a741d0e96f0e 在Android应用开发过程中,构建具有视觉吸引力的用户界面扮演着关键角色,卡片效果(CardView)作为一种常见的设计组件,经常被应用于信息展示或实现滑动浏览功能,例如在Google Play商店中应用推荐的部分。 提及的“一行代码实现ViewPager卡片效果”实际上是指通过简便的方法将CardView与ViewPager整合,从而构建一个可滑动切换的卡片式布局。 接下来我们将深入探讨如何达成这一功能,并拓展相关的Android UI设计及编程知识。 首先需要明确CardView和ViewPager这两个组件的功能。 CardView是Android支持库中的一个视图容器,它提供了一种便捷定制的“卡片”样式,能够包含阴影、圆角以及内容间距等效果,使得内容呈现为悬浮在屏幕表面的形式。 而ViewPager是一个支持左右滑动查看多个页面的控件,通常用于实现类似轮播图或Tab滑动切换的应用场景。 为了实现“一行代码实现ViewPager卡片效果”,首要步骤是确保项目已配置必要的依赖项。 在build.gradle文件中,应加入以下依赖声明:```groovydependencies { implementation androidx.recyclerview:recyclerview:1.2.1 implementation androidx.cardview:cardview:1.0.0}```随后,需要设计一个CardView的布局文件。 在res/layout目录下,创建一个XML布局文件,比如命名为`card_item.xml`,并定义CardView及其内部结构:```xml<and...
下载前可以先看下教程 https://pan.quark.cn/s/fe65075d5bfd 在电子技术领域,熟练运用一系列专业术语对于深入理解和有效应用相关技术具有决定性意义。 以下内容详细阐述了部分电子技术术语,这些术语覆盖了从基础电子元件到高级系统功能等多个层面,旨在为读者提供系统且全面的认知。 ### 执行器(Actuator)执行器是一种能够将电能、液压能或气压能等能量形式转化为机械运动或作用力的装置,主要用于操控物理过程。 在自动化与控制系统领域,执行器常被部署以执行精确动作,例如控制阀门的开闭、驱动电机的旋转等。 ### 放大器(Amplifier)放大器作为电子电路的核心组成部分,其根本功能是提升输入信号的幅度,使其具备驱动负载或满足后续电路运作的能力。 放大器的种类繁多,包括电压放大器和功率放大器等,它们在音频处理、通信系统、信号处理等多个领域得到广泛应用。 ### 衰减(Attenuation)衰减描述的是信号在传输过程中能量逐渐减弱的现象,通常由介质吸收、散射或辐射等因素引发。 在电信号传输、光纤通信以及无线通信领域,衰减是影响信号质量的关键因素之一,需要通过合理的设计和材料选择来最小化其影响。 ### 开线放大器(Antenna Amplifier)开线放大器特指用于增强天线接收信号强度的专用放大器,常见于无线电通信和电视广播行业。 它通常配置在接收设备的前端,旨在提升微弱信号的幅度,从而优化接收效果。 ### 建筑声学(Architectural Acoustics)建筑声学研究声音在建筑物内部的传播规律及其对人类听觉体验的影响。 该领域涉及声波的反射、吸收和透射等物理现象,致力于营造舒适且健康的听觉空间,适用于音乐厅、会议室、住宅等场所的设计需求。 ### 模拟控制...
先看效果: https://pan.quark.cn/s/463a29bca497 《基坑维护施工组织方案》是一项关键性资料,其中详细阐述了在开展建筑施工过程中,针对基坑实施安全防护的具体措施与操作流程。 基坑维护作为建筑工程中不可或缺的一部分,其成效直接关联到整个工程的安全性、施工进度以及周边环境可能产生的影响。 以下内容基于该压缩包文件的核心信息,对相关技术要点进行了系统性的阐释:1. **基坑工程概述**:基坑工程指的是在地面以下构建的临时性作业空间,主要用途是建造建筑物的基础部分。 当基坑挖掘完成之后,必须对周边土壤实施加固处理,以避免土体出现滑动或坍塌现象,从而保障施工的安全性。 2. **基坑分类**:根据地质状况、建筑规模以及施工方式的不同,基坑可以被划分为多种不同的类别,例如放坡式基坑、设置有支护结构的基坑(包括钢板桩、地下连续墙等类型)以及采用降水措施的基坑等。 3. **基坑规划**:在规划阶段,需要综合考量基坑的挖掘深度、地下水位状况、土壤特性以及邻近建筑物的距离等要素,从而制定出科学合理的支护结构计划。 此外,还需进行稳定性评估,以确保在施工期间基坑不会出现失稳问题。 4. **施工安排**:施工组织计划详细规定了基坑挖掘、支护结构部署、降水措施应用、监测与检测、应急响应等各个阶段的工作顺序、时间表以及人员安排,旨在保障施工过程的有序推进。 5. **支护构造**:基坑的支护通常包含挡土构造(例如土钉墙、锚杆、支撑梁)和防水构造(如防渗帷幕),其主要功能是防止土体向侧面移动,维持基坑的稳定状态。 6. **降水方法**:在地下水位较高的区域,基坑维护工作可能需要采用降水手段,例如采用井点降水技术或设置集水坑进行排水,目的是降低地下水位,防止基坑内部积水对...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值