Java多线程之synchronized和volatile
概述
用Java来开发多线程程序变得越来越常见,虽然Java提供了并发包来简化多线程程序的编写,但是我们有必要深入研究一下,才能更好的掌握这块知识。
本文主要对Java提供的底层原语synchronized和volatile进行分析,看看他们究竟干了什么,以及怎么样才能合理的使用它们。
运算速度与IO速度的问题
现代计算机模型,待计算的数据主要存储在内存中,CPU想要对数据进行计算,就必须要经过下面的流程:从内存读取数据-->CPU计算-->把计算结果写回内存。但是,不得不承认一个事实,就是CPU与内存之间的IO速度,要比CPU的运算速度慢很多,所以这样就不能充分发挥CPU的计算能力。
为了解决这个问题,引入了高速缓存的概念,它一般位于内存与CPU之间,与CPU之间有较高的IO速度。高速缓存存放常用的数据,这样就可以大幅度提高CPU的利用率。
由缓存命中问题引出的指令重排序
上面提到了高速缓存之间的概念。既然提到高速缓存,那么就要谈到缓存命中的问题,如果缓存命中率很高,那么整体性能就会提升。所以,试想一下,有下面几行代码:
int a = 0;
a = a + 10;
int b = 0;
b = b + 5;
a = a * 2;
通常我们会认为,执行完第2条代码后,a被写入到高速缓存,然后执行对b的操作,最后再从高速缓存中读取出a,再对a做乘法计算。
但是,现代编译器都会对这种情况做优化。考虑一个问题,既然两次对a操作的指令之间,没有使用到a的指令,那么为什么不在对a做完加法后,直接再对a做个乘法呢。反正又不会影响整个代码的语义,而且还能避免高速缓存不命中的问题,万一这之间执行的代码很多,然后a被清理出了高速缓存,那么到最后执行对a的乘法时,又要从内存中读取,这样看来,很不划算,所以就出现了指令重排。就是说指令真正的执行顺序,不一定是按照代码书写的顺序执行的,可能会打乱顺序执行,但是只要不影响整段代码的语义就行了,因为它们是“好像是串行执行”的方式。
比如下面的代码,如果对a的操作真的发生在对b的操作之前,那么就改变了整段代码的语义,所以这时候就不会重排指令。
int a = 0;
a = a + 10;
int b = 0;
b = b + a;
a = a * 2;
Java的存储(内存)结构
内存中,主要分为两大块区域:
1、全局区域,所有线程共享该区域。
2、线程私有区域,存放需要使用的全局数据的副本。
存储结构:
第一层,位于CPU内部的高速缓存,为了解决CPU与内存之间IO速度和CPU执行速度之间差距太大的问题。
第二层:内存(包括全局区域和线程私有区域)。
对一个变量的操作,通常需要经过下面的这8个步骤(如果不是同步操作,没有1和8)。见下图的蓝色字。

多线程要考虑的问题
1、内存一致性
某个变量,它在主内存中的值,应该和在线程工作内存中的值是一致的。
2、内存可见性
某个变量,如果一个线程对它进行修改,那么其他线程应该能立即看到它的变化。
3、有序性
如果一个线程A依赖另一个线程B的执行结果,那么在线程B看来是串行执行的指令(其实可能经过了指令重排),在线程A看来,就是一个错误的执行顺序。比如下面的情况:
| Thread 1 | Thread 2 |
|---|---|
| x = 1; | int r1 = y; |
| y = 2; | int r2 = x; |
有可能出现 r1 = 2 、r2 = 0 的情况,因为Thread1可能会进行指令重排,所以对于Thread2来说,就发生了错误。
synchronized工作原理
synchronized关键字,通过第1步,对主内存中变量加锁的操作(lock),获得了该变量的使用权,随后,其他线程没有访问这个被加锁变量的权利,一直到该线程使用完成,然后解锁该变量(unlock),之后其他线程才能访问该变量,当然也能看到该变量最新的结果。
由于synchronized以一种让上述8个步骤原子执行的方式工作,所以,它解决了内存一致性的问题,内存可见性的问题、有序性的问题。
使用场景:所有在有多线程共享数据的地方,都可以使用,简单粗暴,但是会引降低性能。
volatile工作原理
volatile关键字,通俗点来说,就是对一个变量的操作,2(read) 、3(load)、 4(use)这3个操作必须是原子的,而
5(assign)、6(store)、7(write)这3个操作也必须是原子的。
那么我们来看看,它能解决什么问题,因为简单来说就是,读(2 3 4)和写(5 6
7)操作分别都是原子的,相当于CPU每次都是直接和内存交互,所以高速缓存就变得无效了。而高速缓存变得无效,那么基本上,也就没有指令重排了。而由于每次改动都会直接写到内存,每次使用都从内存读取新的数据,所以也就满足了内存可见性。但是由于在读和写之间,其他线程也可以进行读和写,那么还是会出现内存一致性问题的。所以它解决了内存可见性、有序性、但是不能解决内存可见性。
使用场景:由于volatile的特性,主要可以使用在下面的场景
1、不依赖于变量之前的状态的,比如一个 volatile boolean 来在一个线程中控制另一个线程的运行。
2、禁止指令重排,比如一个线程依赖于另一个线程真正的顺序执行结果,而不是语义上的顺序执行(其实是经过指令重排的)。
Java多线程之synchronized和volatile的更多相关文章
- JAVA多线程之synchronized和volatile实例讲解
在多线程中,提到线程安全.线程同步,我们经常会想到两个关键字:volatile和synchronized,那么这两者有什么区别呢? 1. volatile修饰的变量具有可见性 volatile是变量修 ...
- (二)java多线程之synchronized
本人邮箱: kco1989@qq.com 欢迎转载,转载请注明网址 http://blog.csdn.net/tianshi_kco github: https://github.com/kco198 ...
- JAVA多线程之Synchronized关键字--对象锁的特点
一,介绍 本文介绍JAVA多线程中的synchronized关键字作为对象锁的一些知识点. 所谓对象锁,就是就是synchronized 给某个对象 加锁.关于 对象锁 可参考:这篇文章 二,分析 s ...
- JAVA多线程之Synchronized、wait、notify实例讲解
一.Synchronized synchronized中文解释是同步,那么什么是同步呢,解释就是程序中用于控制不同线程间操作发生相对顺序的机制,通俗来讲就是2点,第一要有多线程,第二当多个线程同时竞争 ...
- Java多线程之synchronized及其优化
Synchronized和同步阻塞synchronized是jvm提供的同步和锁机制,与之对应的是jdk层面的J.U.C提供的基于AbstractQueuedSynchronizer的并发组件.syn ...
- Java多线程之synchronized详解
目录 synchronized简介 同步的原理 对象头与锁的实现 锁的优化与升级 Monitor Record 锁的对比 synchronized简介 synchronized关键字,一般称之为&qu ...
- Java多线程之synchronized(四)
前面几章都是在说synchronized用于对象锁,无论是修饰方法也好修饰代码块也好,然而关键字synchronized还可以应用到static静态方法上,如果这样写,那就是对当前的*.java文件所 ...
- Java多线程之synchronized(三)
在多线程访问同一个对象中的不同的synchronized方法或synchronized代码块的前提下,也就是“对象监控器”为同一个对象的时候,也就是synchronized的锁为同一把锁的时候,调用的 ...
- java 多线程之synchronized wait/notify解决买票问题
一.Java线程具有五中基本状态 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread(); 就绪状态(Runnable):当调用线程对象的st ...
随机推荐
- Python - with open()、os.open()、open()的详细使用
读写文件背景 读写文件是最常见的IO操作.Python内置了读写文件的函数,用法和C是兼容的. 在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘. 读写文件就是请求 ...
- Android 6.0(棉花糖)新特性
1.支持4K显示 Android 6.0本身已经支持4K显示,会通过一定优化形式使4K内容更加清晰. 2. 启动验证 (更完整的应用权限管理) Android 6.0在开机时会自动运行验证代码,检测设 ...
- delphi制作res文件
第一步:将brcc32.exe拷贝到某个目录,如“res文件”第二步:制作rc文件1.在“res文件”中新建一个文本文件resources.rc:2.文本文件中每一行写一个资源,资源格式为:资源标识名 ...
- Spring(七)核心容器 - 钩子接口
目录 前言 1.Aware 系列接口 2.InitializingBean 3.BeanPostProcessor 4.BeanFactoryPostProcessor 5.ImportSelecto ...
- electron 安装过程出现未成功地运行
问题 正文 产生问题得原因? 是因为之前安装了该程序,但是卸载的时候可能人为的直接删除了卸载程序. 这时候安装包会触发找到注册表中,该appid相同地址的卸载程序位置,然后进行调用,如果没有的话,只会 ...
- k8s~部署EFK框架
EFK,ELK都是目前最为流行的分布式日志框架,主要实现了日志的收集,存储,分析等,它可以与docker容器进行结合,来收集docker的控制台日志,就是stdout日志. elasticsearch ...
- SharePoint PowerShell SendEmail
前言 最近碰到这样一个需求,用户需要个简单的定时邮件提醒,就是抓取SharePoint某个列表里的值,然后作为邮件地址/邮件主题/邮件内容发送出去. 自己想了想,既然用户要求每天定时发送,那么肯定是任 ...
- Android中点击按钮获取星级评分条的评分
场景 效果 注: 博客: https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸道的程序猿 获取编程相关电子书.教程推送与免费下载. 实现 将布局改为Lin ...
- OHEM论文笔记
目录 引言 Fast R-CNN设计思路 一.动机 二.现有方案hard negative mining 及其窘境 hard negative mining实现 窘境 设计思路 OHEM步骤: 反向传 ...
- Java设计模式之Iterator
public interface Aggregate { //调用iterator方法生成实现Iterator接口的类的实例 public abstract Iterator iterator(); ...