你应该要理解的java并发关键字volatile

提高java的并发编程,就不得不提volatile关键字,不管是在面试还是实际开发中 volatile都是一个应该掌握的技能。他的重要性不言而喻。因此也有必要学好。

一、为什么要用到volatile关键字?

使用一个新技术的原因肯定是当前存在了很多问题,在Java多线程的开发中有三种特性:原子性、可见性和有序性。我们可以在这里简单的说一下:

1、原子性(Atomicity

原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行,就好比你做一件事,要么不做,要么做完。java提供了很多机制来保证原子性。我们举一个例子,比如说常见的a++就不满足原子性。这个操作实际是a = a + 1;是可分割的。在运行的时候可能做了一半不做了。所以不满足原子性。

为了解决上面a++出现的问题,java提供了很多其他的关键字和类,比如说AtomicIntegerAtomicLongAtomicReference等。

2、可见性(Visibility)

可见性就是指当一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改。如果我们学过java内存模型的话,对下面这张图想必不陌生:

每一个线程都有一份自己的本地内存,所有线程共用一份主内存。如果一个线程对主内存中的数据进行了修改,而此时另外一个线程不知道是否已经发生了修改,就说此时是不可见的。

这种不可见的状况会带来一个问题,两个线程有可能会操作同一份但是值不一样的数据。这时候怎么办呢?于是乎,今天的主角登场了,这就是volatile关键字。

volatile关键字的作用很简单,就是一个线程在对主内存的某一份数据进行更改时,改完之后会立刻刷新到主内存。并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。这样一来就保证了可见性

3、有序性

程序执行的顺序按照代码的先后顺序执行就叫做有序性,但是有时候程序的执行并不会遵循,比如说下面的代码:

int i = 0; int j = 2;

这两行代码很简单,i=1,j=2,程序在运行的时候一定会先让i=1,然后j=2嘛?不一定,为什么会不一定,这是因为有可能会发生指令重排序,从名字看就知道,在运行的时候,代码会重新排列。这里面涉及到的就比较多了。我会在专门的文章中进行讲解。

为了防止上面的重排序,java依然提供了很多机制,比如volatile关键字等。这也是我们volatile关键字第二个使用的场景。

在上面我们从java并发编程的三个特征来分析了为什么会用到volatile关键字,主要是保证内存可见性和防止指令重排序。下面我们就来正式来分析一下这个volatile

二、深入剖析

1volatile保证原子性嘛?

在上面我们只说了volatile关键字会保证可见性和有序性,但是并没有说会不会保证原子性,原子性的概念我们已经说了,也就是一个操作,要么不执行,要么执行到底。我们可以使用代码来验证一下:

这段代码的含义是,有5个线程,每一个线程都对a进行递增。每个线程一次加10个数。按道理来讲,如果volatile关键字保证原子性的话,最后结果一定是50。我们可以运行一下看看结果:

最后得出的结论就是volatile不保证原子性。既然不能保证原子性,那肯定就是非线程安全的。

2、单例模式的双重锁为什么要加volatile

什么是双重锁的单例模式,我们给出代码可以看看。

这就是单例模式的双重锁实现,为什么这里要加volatile关键字呢?我们把test2 = new Test2()这行代码进行拆分。可以分解为3个步骤:

1memory=allocate();// 分配内存

2ctorInstanc(memory) //初始化对象

3test2=memory //设置s指向刚分配的地址

如果没有volatile关键字,可能会发生指令重排序。在编译器运行时,从1-2-3 排序为1-3-2。此时两个线程同时进来的时候出现可见性问题,也就是说一个线程执行了1-3,另外一个线程一进来直接返回还未执行2null对象。而我们的volatile关键之前已经说过了,可以很好地防止指令重排序。也就不会出现这个问题了。

如果我们学过java并发系列的其他类比如说Atomic等,通过源码我们会发现volatile无处不在。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在信号处理领域,DOA(Direction of Arrival)估计是一项关键技术,主要用于确定多个信号源到达接收阵列的方向。本文将详细探讨三种ESPRIT(Estimation of Signal Parameters via Rotational Invariance Techniques)算法在DOA估计中的实现,以及它们在MATLAB环境中的具体应用。 ESPRIT算法是由Paul Kailath等人于1986年提出的,其核心思想是利用阵列数据的旋转不变性来估计信号源的角度。这种算法相比传统的 MUSIC(Multiple Signal Classification)算法具有较低的计算复杂度,且无需进行特征值分解,因此在实际应用中颇具优势。 1. 普通ESPRIT算法 普通ESPRIT算法分为两个主要步骤:构造等效旋转不变系统和估计角度。通过空间平移(如延时)构建两个子阵列,使得它们之间的关系具有旋转不变性。然后,通过对子阵列数据进行最小二乘拟合,可以得到信号源的角频率估计,进一步转换为DOA估计。 2. 常规ESPRIT算法实现 在描述中提到的`common_esprit_method1.m`和`common_esprit_method2.m`是两种不同的普通ESPRIT算法实现。它们可能在实现细节上略有差异,比如选择子阵列的方式、参数估计的策略等。MATLAB代码通常会包含预处理步骤(如数据归一化)、子阵列构造、旋转不变性矩阵的建立、最小二乘估计等部分。通过运行这两个文件,可以比较它们在估计精度和计算效率上的异同。 3. TLS_ESPRIT算法 TLS(Total Least Squares)ESPRIT是对普通ESPRIT的优化,它考虑了数据噪声的影响,提高了估计的稳健性。在TLS_ESPRIT算法中,不假设数据噪声是高斯白噪声,而是采用总最小二乘准则来拟合数据。这使得算法在噪声环境下表现更优。`TLS_esprit.m`文件应该包含了TLS_ESPRIT算法的完整实现,包括TLS估计的步骤和旋转不变性矩阵的改进处理。 在实际应用中,选择合适的ESPRIT变体取决于系统条件,例如噪声水平、信号质量以及计算资源。通过MATLAB实现,研究者和工程师可以方便地比较不同算法的效果,并根据需要进行调整和优化。同时,这些代码也为教学和学习DOA估计提供了一个直观的平台,有助于深入理解ESPRIT算法的工作原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值