并发编程-06线程安全性之可见性 (synchronized + volatile)

简介: 并发编程-06线程安全性之可见性 (synchronized + volatile)

脑图

20190216205207715.png

可见性定义

一个线程对共享变量值的修改,能够及时的被其他线程看到。


导致不可见的原因

  • 线程交叉执行
  • 重排序结合线程交叉执行
  • 共享变量更新后的值没有在工作内存与主内存之间及时更新

结合我们前面说过的Java内存模型,上述三个原因我们就很容易理解了。 不清楚的童鞋可以再回顾下 并发编程-02并发基础CPU多级缓存和Java内存模型JMM


可见性 -synchronized (既保证原子性又保证可见性)


synchronized能够实现原子性和可见性 。

JMM中关于Synchronized的规定


  • 线程解锁前,必须把共享变量的最新值刷新到主内存
  • 线程加锁时,必须将工作内存中的共享变量的值清空,从而使用共享变量时需要从主内存中重新读取最新的值。【注意,加锁和解锁必须是同一把锁


可见性 - volatile(但不保证操作的原子性)


volatile可以保证 可见性和 有序性

通过加入内存屏障禁止重排序优化来实现。

  • 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
  • 对volatile变量读操作,会在读操作前加入一条load指令屏障,从主内存中读取共享变量


volatile本质是在告诉JVM当前变量在寄存器中的值是不确定的,使用前,需要先从主存中读取,因此可以实现可见性。而对n=n+1,n++等操作时,volatile关键字将失效,不能起到像synchronized一样的线程同步(原子性)的效果。


volatile变量 写操作


20190217224828806.png

volatile变量 读操作


20190217224921630.png


使用volatile尝试解决计数并发错误的问题 【volatile无法解决该问题】


2019021723151039.png

即使将count用volatile修饰,每次从主存中取到的都是最新的值,可是当多个线程同时取到最新的值,执行+1操作,当刷新到主存中的时候会覆盖结果,从而丢失一些+1操作


volatile使用场景


多线程中使用volatile变量,对变量的写入操作不能依赖当前变量的值:如count++ .【 解释下: count++不是原子操作,因为其可以分为:从主内存中读取count的值,在自己线程的工作内存中将count的值+1,写入最新的count的值到主内存。 对于count++,线程A和线程B都执行一次,最后输出的count的值可能是1也可能是2】 。 比较适合 状态标记 场景

状态标记伪代码

volatile  boolean inited = false;
// 线程A
context = loadContext();
inited = true;
// 线程B
while(!inited ){
  sleep();
}
doSomethingWithConfig(context);


将inited 标记为 volatile , 当线程A更新inited后,因为是volatile,所以线程B可以及时从主内存中感知到inited的改变。 这样就确保了线程B中使用的contex是初始化过的。


double check (比较常见的比如单例模式中的double check)


synchronized和volatile的比较


  • synchronized保证内存可见性和操作的原子性
  • volatile只能保证内存可见性
  • volatile不需要加锁,比synchronized更轻量级,并不会阻塞线程
  • volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化(如编译器重排序的优化).
  • volatile是变量修饰符,仅能用于变量,而synchronized是一个方法或块的修饰符。

代码

https://github.com/yangshangwei/ConcurrencyMaster

相关文章
|
3月前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
Java多线程同步大揭秘:synchronized与Lock的终极对决!
73 5
|
4天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
19 4
|
28天前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
【10月更文挑战第6天】在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
23 2
|
29天前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
29 3
|
2月前
|
负载均衡 Java 调度
探索Python的并发编程:线程与进程的比较与应用
本文旨在深入探讨Python中的并发编程,重点比较线程与进程的异同、适用场景及实现方法。通过分析GIL对线程并发的影响,以及进程间通信的成本,我们将揭示何时选择线程或进程更为合理。同时,文章将提供实用的代码示例,帮助读者更好地理解并运用这些概念,以提升多任务处理的效率和性能。
57 3
|
2月前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
1月前
|
Java 编译器 程序员
【多线程】synchronized原理
【多线程】synchronized原理
51 0
|
3月前
|
安全 Java 开发者
Java多线程同步:synchronized与Lock的“爱恨情仇”!
Java多线程同步:synchronized与Lock的“爱恨情仇”!
85 5
|
3月前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
28 2
|
2月前
|
并行计算 API 调度
探索Python中的并发编程:线程与进程的对比分析
【9月更文挑战第21天】本文深入探讨了Python中并发编程的核心概念,通过直观的代码示例和清晰的逻辑推理,引导读者理解线程与进程在解决并发问题时的不同应用场景。我们将从基础理论出发,逐步过渡到实际案例分析,旨在揭示Python并发模型的内在机制,并比较它们在执行效率、资源占用和适用场景方面的差异。文章不仅适合初学者构建并发编程的基础认识,同时也为有经验的开发者提供深度思考的视角。