如何使用 volatile, synchronized, final 进行线程间通信
2019独角兽企业重金招聘Python工程师标准>>>
你是否真正理解并会用volatile, synchronized, final
进行线程间通信呢,如果你不能回答下面的几个问题,那就说明你并没有真正的理解:
对
volatile
变量的操作一定具有原子性吗?synchronized
所谓的加锁,锁住的是什么?final
定义的变量不变的到底是什么?
java内存模型
内存模型
看java内存模型之前,我们先来看看什么是内存模型?
在多处理器系统中,处理器通常有多级缓存,因为这些缓存离处理器更近并且可以存储一部分数据,所以缓存可以改善处理器获取数据的速度和减少对共享内 存数据总线的占用。缓存虽然能极大的提高性能,但是同时也带来了诸多挑战。例如,当两个处理器同时操作同一个内存地址的时候,该如何处理?这两个处理器在 什么条件下才能看到相同的值?
对于处理器而言,一个内存模型就是定义一些充分必要的规范,这些规范使得其他处理器对内存的写操作对当前处理器可见,或者当前处理器的写操作对其他处理器可见。
其他处理器对内存的写一定发生在当前处理器对同一内存的读之前,称之为其他处理器对内存的写对当前处理器可见。
Java 内存模型
知道了内存模型,那么应该可以更好的理解java内存模型。
简单的讲,java内存模型指的就是一套规范,现在最新的规范为JSR-133。这套规范包含:
线程之间如何通过内存通信;
线程之间通过什么方式通信才合法,才能得到期望的结果。
Java 内存模型中的内存结构
我们已经知道 java 内存模型就是一套规范,那么在这套规范中,规定的内存结构是什么样的呢?
简单的讲,Java 内存模型将内存分为共享内存和本地内存。共享内存又称为堆内存,指的就是线程之间共享的内存,包含所有的实例域、静态域和数组元素。每个线程都有一个私有的,只对自己可见的内存,称之为本地内存。
java内存模型中的内存结构如下图所示:
共享内存中共享变量虽然由所有的线程共享,但是为了提高效率,线程并不直接使用这些变量,每个线程都会在自己的本地内存中存储一个共享内存的副本,使用这个副本参与运算。由于这个副本的参与,导致了线程之间对共享内存的读写存在可见性问题。
为了方便线程之间的通信,java 提供了 volatile, synchronized, final 三个关键字供我们使用,下面我们来看看如何使用它们进行线程间通信
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发送了消息。
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更新之后的。
volatile 和 synchronized比较
根据以上的分析,我们可以发现volatile和synchronized有些相似。
当线程对 volatile变量写时,java 会把值刷新到共享内存中;而对于synchronized,指的是当线程释放锁的时候,会将共享变量的值刷新到主内存中。
线程读取volatile变量时,会将本地内存中的共享变量置为无效;对于synchronized来说,当线程获取锁时,会将当前线程本地内存中的共享变量置为无效。
synchronized 扩大了可见影响的范围,扩大到了synchronized作用的代码块。
final 变量
final关键字可以作用于变量、方法和类,我们这里只看final 变量。
final变量的特殊之处在于,
final 变量一经初始化,就不能改变其值。
这里的值对于一个对象或者数组来说指的是这个对象或者数组的引用地址。因此,一个线程定义了一个final变量之后,其他任意线程都拿到这个变量。但有一点需要注意的是,当这个final变量为对象或者数组时,
虽然我们不能讲这个变量赋值为其他对象或者数组,但是我们可以改变对象的域或者数组中的元素。
线程对这个对象变量的域或者数据的元素的改变不具有线程可见性。
总结
有时候,我们在学习一门技术过程中,并不能仅仅局限于怎么用,知道怎么用之后,我们应该深入的探究一下,为什么这么用之后就能得到我们想要的结果呢?既要知其然,更要知其所以然。
转载于:https://my.oschina.net/u/131940/blog/624822
如何使用 volatile, synchronized, final 进行线程间通信相关推荐
- Java多线程:线程间通信之volatile与sychronized
由前文Java内存模型我们熟悉了Java的内存工作模式和线程间的交互规范,本篇从应用层面讲解Java线程间通信. Java为线程间通信提供了三个相关的关键字volatile, synchronized ...
- (转)C++进程间和线程间通信
进程间和线程间通信(原文章出自http://blog.sina.com.cn/s/blog_4a93ccea0102ea1w.html) 1.许多程序和应用一起工作达到某个共同目的的任务集.每个 ...
- Java多线程:线程间通信之Lock
Java 5 之后,Java在内置关键字sychronized的基础上又增加了一个新的处理锁的方式,Lock类. 由于在Java线程间通信:volatile与sychronized中,我们已经详细的了 ...
- 【JUC】第二章 线程间通信、集合的线程安全
第二章 线程间通信.集合的线程安全 文章目录 第二章 线程间通信.集合的线程安全 一.线程间通信 1.介绍 2.synchronized 方案 3.Lock 方案 4.定制化线程通信 二.集合的线程安 ...
- java synoch 加锁_线程间通信 - HappyCowboy - 博客园
线程之间需要一些协调通信,来共同完成一件任务.Java多线程中,线程之间通信最常用的两个方法是wait()与notify() 使用wait()与notify()实现线程间的通信,需注意: ①wait( ...
- 线程间通信---数字交替打印(循环打印abc)
今天来看看面试中必考的线程间通信,经历过面试的都知道,手撕代码除了算法题,排序题,单例模式等之外就是线程间通信的考点了.来一起喵喵吧. 1.两个数字交替打印 1.1 自定义锁 使用volatile保证 ...
- JAVA线程间通信的几种方式
今天在群里面看到一个很有意思的面试题: "编写两个线程,一个线程打印1~25,另一个线程打印字母A~Z,打印顺序为12A34B56C--5152Z,要求使用线程间的通信." 这是一 ...
- Java并发——线程间通信与同步技术
传统的线程间通信与同步技术为Object上的wait().notify().notifyAll()等方法,Java在显示锁上增加了Condition对象,该对象也可以实现线程间通信与同步.本文会介绍有 ...
- 【转】Java里如何实现线程间通信
正常情况下,每个子线程完成各自的任务就可以结束了.不过有的时候,我们希望多个线程协同工作来完成某个任务,这时就涉及到了线程间通信了. 本文涉及到的知识点:thread.join(), object.w ...
最新文章
- 四年努力,梦归阿里,和大家聊聊成长感悟
- 滑动窗口算法应用及详解
- memoryerror: Unable to allocate array with shape (60000, 28, 28) and data ty
- BAT大厂工程师必会Ansible,你还不来学?
- php+node+redis,redis,nodejs,php,pub/sub 实战: 微信语音识别
- Kubernetes探针踩坑记
- IntelliJ IDEA 配置JDK
- 算法学习之剑指offer(六)
- 数据结构与算法(C#实现)系列---树
- svn导入 ubuntu_ubuntu搭建SVN服务器详细教程
- 在kettle中实现数据验证和检查
- 学自动化测试哪个培训机构好 试听课程后就选了这个地方学习
- 最新xcode打包IPA----2021.1.18(完整详细图文)
- [原]终于有新机器用了Intel E6500K+4G+23.6’液晶
- 编写函数:字符串的小写转大写(Append Code)
- Centos 7系统密码破解
- 岩板铺地好吗_岩板铺客厅地面好吗 比800*800的瓷砖更美观又大气?
- 【已解决】Nginx基于多端口、多域名配置
- c程序语言捉迷藏,小学一年级语文《捉迷藏》教案范文
- iPhone 14微信闪退怎么办?iPhone 14微信闪退解决办法分享
热门文章
- 如何合并mysql中的行_如何在MySQL中合并行?
- 自组网中继台_家庭组网无线漫游方案--Mesh 组网
- 20190817:(leetcode习题)旋转图像
- idea上一步下一步快捷键_领航者的一步,左右行业的下一步!双11海尔洗衣机再夺冠的思考...
- 达梦数据库查看某个表的字段类型、常用数据库驱动类名以及URL
- oracle中varchar与varchar2的区别
- oracle将把varchar2字段(长度4000)改为clob类型
- VB 各种进制相互转换大全
- 浏览器上跑:TensorFlow发布实时人物分割模型,秒速25帧,24个部位
- 特斯拉要退市!马斯克内部信:不受华尔街摆布;股价大涨10%