搜公众号
推荐 原创 视频 Java开发 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库
Lambda在线 > 架构师必备 > 凛冬将至,夯实基础—从Java内存模型理解synchronized、volatile和final关键字

凛冬将至,夯实基础—从Java内存模型理解synchronized、volatile和final关键字

架构师必备 2019-04-16
举报

 你是否真正理解并会用volatile, synchronized, final进行线程间通信呢,如果你不能回答下面的几个问题,那就说明你并没有真正的理解:

        1、对volatile变量的操作一定具有原子性吗?(原子操作是不需要synchronized来保护的,所谓原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程切换)

        2、synchronized所谓的加锁,锁住的是什么?

        3、final定义的变量不变的到底是什么?

1、Java内存模型

        看Java内存模型之前,我们先来了解什么是内存模型?

        对于处理器而言,一个内存模型就是定义一些充分必要的规范,这些规范使得其他处理器对内存的写操作对当前处理器可见,或者当前处理器的写操作对其他处理器可见。

        其他处理器对内存的写一定发生在当前处理器对同一内存的读之前,称之为其他处理器对内存的写对当前处理器可见。

        知道了内存模型,那么应该可以更好的理解java内存模型。简单的讲,java内存模型指的就是一套规范,现在最新的规范为JSR-133。这套规范包含:

        1、线程之间如何通过内存通信;

        2、线程之间通过什么方式通信才合法,才能得到期望的结果。

        我们已经知道 java 内存模型就是一套规范,那么在这套规范中,规定的内存结构是什么样的呢?java内存模型中的内存结构如下图所示:


        简单的讲,Java 内存模型将内存分为共享内存和本地内存。共享内存又称为堆内存,指的就是线程之间共享的内存,包含所有的实例域、静态域和数组元素。每个线程都有一个私有的,只对自己可见的内存,称之为本地内存

        共享内存中共享变量虽然由所有的线程共享,但是为了提高效率,线程并不直接使用这些变量,每个线程都会在自己的本地内存中存储一个共享内存的副本,使用这个副本参与运算。由于这个副本的参与,导致了线程之间对共享内存的读写存在可见性问题

        为了方便线程之间的通信,java 提供了 volatile, synchronized, final 三个关键字供我们使用,下面我们来看看如何使用它们进行线程间通信。

2、volatile关键字


        volatile 定义的变量,特殊性在于:

        一个线程对 volatile 变量的写一定对之后对这个变量的读的线程可见。

        等价于

        一个线程对 volatile 变量的读一定能看见在它之前最后一个线程对这个变量的写。

        为了实现这些语义,Java 规定,(1)当一个线程要使用共享内存中的 volatile 变量时,如图中的变量a,它会直接从主内存中读取,而不使用自己本地内存中的副本。(2)当一个线程对一个 volatile 变量进行写时,它会将这个共享变量的值刷新到共享内存中。

        我们可以看到,其实 volatile 变量保证的是一个线程对它的写会立即刷新到主内存中,并置其它线程的副本为无效,它并不保证对 volatile 变量的操作都是具有原子性的。

public void add(){
a++; #1
}

        等价于

public void add() {
temp = a;
temp = temp +1;
a = temp;
}

        代码1并不是一个原子操作,所以类似于 a++ 这样的操作会导致并发数据问题。

        volatile 变量的写被保证是可以被之后其他线程的读看到的,因此我们可以利用它进行线程间的通信。如:

volatile int a;
public void set(int b) {
a = b;
}
public void get() {
int i = a;
}

        线程A执行set()后,线程B执行get(),相当于线程A向线程B发送了消息。

3、synchronized


        如果我们非要使用 a++ 这种复合操作进行线程间通信呢?java 为我们提供了synchronized。

public synchronized void add() {
a++;
}

        synchronized 使得它作用范围内的代码对于不同线程是互斥的,并且线程在释放锁的时候会将共享变量的值刷新到主内存中。

        我们可以利用这种互斥性来进行线程间通信。看下面的代码:

public synchronized void add() {
a++;
}
public synchronized void get() {
int i = a;
}

        当线程A执行 add(),线程B调用get(),由于互斥性,线程A执行完add()后,线程B才能开始执行get(),并且线程A执行完add(),释放锁的时候,会将a的值刷新到共享内存中。因此线程B拿到的a的值是线程A更新之后的。

4、volatile和synchronized的比较


        根据以上的分析,我们可以发现volatile和synchronized有些相似。

        1、当线程对 volatile变量写时,java 会把值刷新到共享内存中;而对于synchronized,指的是当线程释放锁的时候,会将共享变量的值刷新到主内存中。

        2、线程读取volatile变量时,会将本地内存中的共享变量置为无效;对于synchronized来说,当线程获取锁时,会将当前线程本地内存中的共享变量置为无效。

        3、synchronized 扩大了可见影响的范围,扩大到了synchronized作用的代码块。

5、final变量


        final关键字可以修饰变量、方法和类,我们这里只讨论final修饰的变量。final变量的特殊之处在于:

        final 变量一经初始化,就不能改变其值。

        2、线程对这个对象变量的域或者数据的元素的改变不具有线程可见性。

查看文章内容请点击下方 阅读原文 或者访问 算法网http://ddrv.cn/


版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《凛冬将至,夯实基础—从Java内存模型理解synchronized、volatile和final关键字》的版权归原作者「架构师必备」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

举报