浅谈synchronized和volatitle实现线程安全的策略
什么是线程不安全
我对线程安全的理解就是多个线程同时操作一个共享变量时会产生意料之外的情况,这种情况就是线程不安全。注意:只有写操作才可能出现线程不安全,对共享变量只进行读操作线程是绝对安全的。
具体线程不安全的例子有一个很经典的就是两个线程都对一个共享变量x=0执行100次自增操作,但是x的结果并非200
因此线程不安全的条件是:多线程 + 共享变量 + 写操作
Java的内存模型
你可能会好奇线程是如何获取共享变量的,这就为你解答
Java线程之间的通信由Java内存模型(简称JMM)控制,从抽象的角度来说,JMM定义了线程和主内存之间的抽象关系。JMM的抽象示意图如图所示:

从图中我们可以看到:
- 共享变量存在于主内存中,也就是堆内存
- 每一个线程都保存了一份该线程使用到的共享变量的副本
- 线程读取共享变量优先从本地内存(也就是栈内存)中读取,写共享变量先写到栈内存,再写入堆内存
- 线程之间对共享变量的通信只能通过堆内存
以上只是Java内存模型的抽象图,实际上线程的工作模型是这样的,栈内存即是两个缓冲区

接下来看一个线程不安全的例子:
假设线程A、B操作同一个共享变量X,初始两级Cache都为空
- 线程A想要读取X的值,由于两级Cache都没有命中,因此加载堆内存中的X=0,并缓存到两个Cache中
- 线程A修改X的值为1,为为两个Cache刷新X,再刷新到堆内存
- 线程B想要获取X的值,一级缓存没有获取到,二级缓存命中,读取到X=1
- 线程B想要修改X的值为2,先刷新自,己的一级缓存为2,再刷新二级缓存和堆内存中的X为2。目前为止一切正常,接下来重点来了
- 线程A想要读取X的值,一级缓存命中此时X=1,但是堆内存中的X=2。可以看到线程B写入的共享变量对X不可见,出现了线程不安全的情况。
由于Java内存机制就是这样设计的,因此多个线程操作同一个变量会产生不安全的问题,volatitle关键字这是被设计出来解决这一问题的,它只能用于单个变量。
volatile解决共享变量线程不安全的策略
还是接着上面这个例子,我们这样定义X
volatle int X=0
volatile的内存语义是:
当一个线程对volatile 修饰的变量进行写操作时,JMM会立即将该线程对应的栈内存中的副本的值刷新到堆内存中;当一个线程对volatile 修饰的变量进行读时,JMM会清空此变量的一二级缓存,直接从堆内存中读取共享变量的值。
volatile可以当作一个轻量级的锁来使用,但volatile仅仅只能保证共享变量内存的可见性,不能保证操作共享变量的原子性,而锁(如synchronized)能保证整段锁范围内的代码具有原子性。
synchronized与锁
首先要明确的是synchronized不是锁,锁都是基于对象的(Object的子类),Java中的每一个对象都可以作为一个锁。
synchronized是Java的一个关键字,保证临界区内的代码同一时刻只能有一个线程执行。
线程的执行代码在进入synchronized代码块前会自动获取内部锁,这时候其他线程访问该同步代码块时会被阻塞挂起。拿到内部锁的线程会在正常退出同步代码块或者抛出异常后或者在同步块内调用了该内置锁资源的wait系列方法时释放该内置锁。内置锁是排它锁,也就是当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取该锁。
sysnchronized的内部锁可以是:
- 当前类的class字节码对象:this.getClass
- 当前类的一个实例:this
- 一个Object对象
wait和notify方法只能用于synchronized同步代码块内
synchronized的内存语义
与volatile不同
进入synchronized块的内存语义是把再synchronized块内使用到的所有共享变量从栈内存中清空,这样就只能从堆内存只读取,保证了内存可见性。退出synchronized块的内存语义是把synchronized块内对共享变量的修改刷新到堆内存。
仔细想想,这其实也是一个加锁和解锁的过程,保证共享变量修改的可见性。
总结
volatile仅能保证单个共享变量内存的可见性,不能保证原子性。而synchronized既可保证同步块内所有共享变量的内存可见性,又能保证其操作的原子性。volatile是一个轻量级的保证内存可见性的关键字,实际上并没有加锁。因此它的性能很高。synchronized是一个重量级的锁,可以用在代码块、普通方法以及静态方法上。用在代码块时锁就是synchronized(~)内的对象,用在普通方法时锁就是this,用在静态方法时锁就是this.getClass()synchronized保证同步块内代码的原子性,因为要进行线程上下文切换,性能较低。不过优化过后性能也还可以。
参考
- 深入浅出Java多线程
- Java并发编程之美
浅谈synchronized和volatitle实现线程安全的策略的更多相关文章
- 浅谈synchronized
目录 浅谈synchronized 前言 是什么 格式 同步代码块 同步方法 注意 最后 浅谈synchronized 前言 看多线程的相关书籍的时候,会经常阅读到一个使用前景,就是银行的取钱存钱操作 ...
- Java多线程编程(四)—浅谈synchronized与lock
一.共享资源竞争问题 在Java语言的并发编程中,由于我们不知道线程实际上在何时运行,所以在实际多线程编程中,如果两个线程访问相同的资源,那么由于线程运行的不确定性便会在这种多线程中产生访问错误.所以 ...
- 浅谈Java线程安全
浅谈Java线程安全 - - 2019-04-25 17:37:28 线程安全 Java中的线程安全 按照线程安全的安全程序由强至弱来排序,我们可以将Java语言中各种操作共享的数据分为以下五类 ...
- 对kotlin和java中的synchronized的浅谈
synchronized在java中是一个关键字,但是在kotlin中是一个内联函数.假如分别在java和kotlin代码锁住同一个对象,会发生什么呢,今天写了代码试了试.首先定义people类 12 ...
- 浅谈线程池(中):独立线程池的作用及IO线程池
原文地址:http://blog.zhaojie.me/2009/07/thread-pool-2-dedicate-pool-and-io-pool.html 在上一篇文章中,我们简单讨论了线程池的 ...
- 浅谈线程池(上):线程池的作用及CLR线程池
原文地址:http://blog.zhaojie.me/2009/07/thread-pool-1-the-goal-and-the-clr-thread-pool.html 线程池是一个重要的概念. ...
- 转载【浅谈ThreadPool 线程池】
浅谈ThreadPool 线程池 http://www.cnblogs.com/xugang/archive/2010/04/20/1716042.html
- 浅谈ThreadPool 线程池(引用)
出自:http://www.cnblogs.com/xugang/archive/2010/04/20/1716042.html 浅谈ThreadPool 线程池 相关概念: 线程池可以看做容纳线程的 ...
- 【分析】浅谈C#中Control的Invoke与BeginInvoke在主副线程中的执行顺序和区别(SamWang)
[分析]浅谈C#中Control的Invoke与BeginInvoke在主副线程中的执行顺序和区别(SamWang) 今天无意中看到有关Invoke和BeginInvoke的一些资料,不太清楚它们之间 ...
随机推荐
- Navicat Premium的数据传输功能----将远程Mysql数据库复制到本地数据库的方法
1.先连上本地.远程的数据库 2.在本地建一个和你要复制的远程数据库的名称一样的数据库 3.数据转移.工具-->数据传输-->填写源数据库和目标数据库-->下一步-->开始 注 ...
- ElasticSearch7.9.2设置密码
1:设置ElasticSearch的密码 1.1:停止运行ElasticSearch,修改配置. vim elasticsearch-7.9.2/config/elasticsearch.yml 新增 ...
- Python和JavaScript在使用上有什么区别?
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文出处:https://www.freecodecamp.org/news/python-vs-javas ...
- Java流程控制:选择结构
一.选择结构 选择结构用于判断给定的条件,根据判断的结果来控制程序的流程. Java中选择结构的语法主要分为'if...else'语句和'switch...case'语句. Java中选择结构语句在语 ...
- python爬取股票最新数据并用excel绘制树状图
大家好,最近大A的白马股们简直 跌妈不认,作为重仓了抱团白马股基金的养鸡少年,每日那是一个以泪洗面啊. 不过从金融界最近一个交易日的大盘云图来看,其实很多中小股还是红色滴,绿的都是白马股们. 以下截图 ...
- java基础知识 + 常见面试题
准备校招面试之Java篇 一. Java SE 部分 1.1 Java基础 1. 请你解释Object若不重写hashCode()的话,hashCode()如何计算出来的? Object 的 hash ...
- 模式识别Pattern Recognition
双目摄像头,单目摄像头缺少深度 Train->test->train->test->predicive
- 【Arduino学习笔记02】第一个Arduino项目——点亮LED Blink.ino程序解读 Arduino程序基本结构 pinMode() digitalWrite() delay()
/* Blink Turns an LED on for one second, then off for one second, repeatedly. */// define variables ...
- jdk8的安装与环境搭建
jdk8的安装与环境搭建 jdk8下载网址:https://www.oracle.com/cn/java/technologies/javase/javase-jdk8-downloads.html ...
- MindSpore:基于本地差分隐私的 Bandit 算法
摘要:本文将先简单介绍Bandit 问题和本地差分隐私的相关背景,然后介绍基于本地差分隐私的 Bandit 算法,最后通过一个简单的电影推荐场景来验证 LDP LinUCB 算法. Bandit问题是 ...