Web前端最新Java面试-volatile的内存语义_java面试 volatile,android快手面试

结尾

学习html5、css、javascript这些基础知识,学习的渠道很多,就不多说了,例如,一些其他的优秀博客。但是本人觉得看书也很必要,可以节省很多时间,常见的javascript的书,例如:javascript的高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。

html5

* @return
*/
public long get() {
return v1;
}

}


假设有多个线程分别调用上面程序的3个方法,这个程序在语义上和下面程序等价。



package com.lizba.p1;

/**
*


* synchronized等价示例
*


*
* @Author: Liziba
* @Date: 2021/6/9 21:46
*/
public class SynFeatureExample {
/\*\* 定义一个64位长度的普通变量 \*/
long v1 = 0L;

/\*\*

* 使用同步锁对v1变量进行写操作
* @param l
*/
public synchronized void set(long l) {
v1 = l;
}

/\*\*

* 通过同步读和同步写方法对v1进行+1操作
*/
public void getAndIncrement() {
long temp = get();
// v1加一
temp += 1L;
set(temp);
}

/\*\*

* 使用同步锁对v1进行读操作
* @return
*/
public synchronized long get() {
return v1;
}

}


如上两个程序所示,一个volatile变量的单个读\写操作,与一个普通变量的读\写操作都是使用同一个锁来同步,它们之间的执行效果相同。  
 **上述代码总结:**


* **锁的happens-before规则保证释放锁和获取锁的两个线程之间的内存可见性**,这意味着对一个volatile变量的读,**总能看到(任意线程)对这个volatile变量最后的写入。**
* **锁的语义决定了临界区代码的执行具有原子性**。这意味着,即使是64位的long型和double型变量,只要它是volatile变量,对该变量的读/写就具有原子性。如果是多个volatile操作或类似于volatile++这种复合操作,这些操作整体上不具备原子性。


**总结volatile特性:**


1. 可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
2. 原子性。对任意volatile变量的读/写具有原子性,但类似volatile++这种复合操作不具有原子性。


#### 2、volatile写-读建立的happens-before关系


对于程序员来说,我们更加需要关注的是volatile对线程内存的可见性。  
 从JDK1.5(JSR-133)开始,volatile变量的写-读可以实现线程之间的通信。从内存语义的角度来说,volatile的写-读与锁的释放-获取有相同的内存效果。


* volatile的写和锁的释放有相同的内存语义
* volatile的读和锁的获取有相同的内存语义


**代码示例:**



package com.lizba.p1;

/**
*


*
*


*
* @Author: Liziba
* @Date: 2021/6/9 22:23
*/
public class VolatileExample {
int a = 0;

volatile boolean flag = false;

public void writer() {
    a = 1;                              // 1
    flag = true;                        // 2
}

public void reader() {
    if (flag) {                         // 3
        int i = a;                      // 4
        System.out.println(i);
    }
}

}


假设线程A执行writer()方法之后,线程B执行reader()方法。根据happens-before规则,这个过程建立的happens-before关系如下:


1. 根据程序次序规则,1 happens-before 2, 3 happens-before 4。
2. 根据volatile规则,2 happens-before 3。
3. 根据happens-before的传递性规则,1 happens-before 4。


图示上述happens-before关系:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210610002642797.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMTI1MjE5,size_16,color_FFFFFF,t_70#pic_center)


总结:这里A线程写一个volatile变量后,B线程读同一个volatile变量。A线程在写volatile变量之前所有可见的共享变量,在B线程读同一个volatile变量后,将立即对B线程可见。  
 ​


#### 3、volatile写-读的内存语义


##### volatile写的内存语义


当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。  
 以上面的VolatileExample为例,假设A线程首先执行writer()方法,随后线程B执行reader()方法,初始时两个线程的本地内存中的flag和a都是初始状态。  
 **A执行volatile写后,共享变量状态示意图**。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210610002715183.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMTI1MjE5,size_16,color_FFFFFF,t_70#pic_center)


线程A在写flag变量后,本地内存A中被线程A更新过的两个共享变量的值被刷新到主内存中,此时A的本地内存和主内存中的值是一致的。  
   



##### volatile读的内存语义


当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将会从主内存中读取共享变量。  
 **B执行volatile读后,共享变量的状态示意图。**  
 ​![在这里插入图片描述](https://img-blog.csdnimg.cn/20210610002759752.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMTI1MjE5,size_16,color_FFFFFF,t_70#pic_center)


在读flag变量后,本地内存B包含的值已经被置为无效。此时,线程B必须从主内存中重新读取共享变量。线程B的读取操作将导致本地内存B与主内存中的共享变量的值变为一致。  
 ​


**总结volatile的写和volatile读的内存语义**


1. 线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所做修改的)消息。
2. 线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile变量之前对共享变量所做修改的)消息。
3. 线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A通过主内存向线程B发送消息。


​


#### 4、volatile内存语义实现


程序的重排序分为编译器重排序和处理器重排序(我的前面的博文内容有写哈)。为了实现volatile内存语义,JMM会分别禁止这两种类型的重排序。  
 ​


**volatile重排序规则表**




| **是否能重排序** | **第二个操作** | **第二个操作** | **第二个操作** |
| --- | --- | --- | --- |
| **第一个操作** | **普通读/写** | **volatile读** | **volatile写** |
| **普通读/写** |  |  | NO |
| **volatile读** | NO | NO | NO |
| **volatile写** |  | NO | NO |


上图举例:第一行最后一个单元格意思是,在程序中第一个操作为普通读/写时,如果第二个操作为volatile写,则编译器不能重排序。


**总结上图:**


* 第二个操作是volatile写时,都不能重排序。确保volatile写之前的操作不会被编译器重排序到volatile之后
* 第一个操作为volatile读时,都不能重排序。确保volatile读之后的操作不会被编译器重排序到volatile之前
* 第一个操作为volatile写,第二个操作为volatile读时,不能重排序。


为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。


**JMM采取的是保守策略内存屏障插入策略,如下:**


* 在每个volatile写操作屏障前面插入一个StoreStore屏障。
* 在每个volatile写操作的后面插入一个StoreLoad屏障
* 在每个volatile读操作的后面插入一个LoadLoad屏障。
* 在每个volatile读操作的后面插入一个LoadStore屏障。




### 文末

js前端的重头戏,值得花大部分时间学习。

![JavaScript知识](https://img-blog.csdnimg.cn/img_convert/701f4db8e7fc0c3ff4d87017d6c846be.png)

推荐通过书籍学习,《 JavaScript 高级程序设计(第 4 版)》你值得拥有。整本书内容质量都很高,尤其是前十章语言基础部分,建议多读几遍。

![前端电子书](https://img-blog.csdnimg.cn/img_convert/6065b7d33c9a5859971490467a967767.png)

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**

另外,大推一个网上教程 现代 JavaScript 教程 ,文章深入浅出,很容易理解,上面的内容几乎都是重点,而且充分发挥了网上教程的时效性和资料链接。



学习资料在精不在多,二者结合,定能构建你的 JavaScript 知识体系。

面试本质也是考试,面试题就起到很好的考纲作用。想要取得优秀的面试成绩,刷面试题是必须的,除非你样样精通。

**这是288页的前端面试题**

![288页面试题](https://img-blog.csdnimg.cn/img_convert/6a0ed19303290ef201081fc6148f21db.png)

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值