并发编程-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

相关文章
|
8天前
|
Java
并发编程的艺术:Java线程与锁机制探索
【6月更文挑战第21天】**并发编程的艺术:Java线程与锁机制探索** 在多核时代,掌握并发编程至关重要。本文探讨Java中线程创建(`Thread`或`Runnable`)、线程同步(`synchronized`关键字与`Lock`接口)及线程池(`ExecutorService`)的使用。同时,警惕并发问题,如死锁和饥饿,遵循最佳实践以确保应用的高效和健壮。
24 2
|
9天前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
【6月更文挑战第20天】在Java多线程编程中,`synchronized`和`Lock`是两种关键的同步机制。`synchronized`作为内置关键字提供基础同步,简单但可能不够灵活;而`Lock`接口自Java 5引入,提供更复杂的控制和优化性能的选项。在低竞争场景下,`synchronized`性能可能更好,但在高并发或需要精细控制时,`Lock`(如`ReentrantLock`)更具优势。选择哪种取决于具体需求和场景,理解两者机制至关重要。
|
9天前
|
Java 测试技术
Java多线程同步实战:从synchronized到Lock的进化之路!
【6月更文挑战第20天】Java多线程同步始于`synchronized`关键字,保证单线程访问共享资源,但为应对复杂场景,`Lock`接口(如`ReentrantLock`)提供了更细粒度控制,包括可重入、公平性及中断等待。通过实战比较两者在高并发下的性能,了解其应用场景。不断学习如`Semaphore`等工具并实践,能提升多线程编程能力。从同步起点到专家之路,每次实战都是进步的阶梯。
|
3天前
|
存储 设计模式 安全
C++一分钟之-并发编程基础:线程与std::thread
【6月更文挑战第26天】C++11的`std::thread`简化了多线程编程,允许并发执行任务以提升效率。文中介绍了创建线程的基本方法,包括使用函数和lambda表达式,并强调了数据竞争、线程生命周期管理及异常安全等关键问题。通过示例展示了如何用互斥锁避免数据竞争,还提及了线程属性定制、线程局部存储和同步工具。理解并发编程的挑战与解决方案是提升程序性能的关键。
23 3
|
5天前
|
Java
Java中的`synchronized`关键字是一个用于并发控制的关键字,它提供了一种简单的加锁机制来确保多线程环境下的数据一致性。
【6月更文挑战第24天】Java的`synchronized`关键字确保多线程数据一致性,通过锁定代码块或方法防止并发冲突。同步方法整个方法体为临界区,同步代码块则锁定特定对象。示例展示了如何在`Counter`类中使用`synchronized`保证原子操作和可见性,同时指出过度使用可能影响性能。
19 4
|
12天前
|
数据挖掘 调度 开发者
Python并发编程的艺术:掌握线程、进程与协程的同步技巧
并发编程在Python中涵盖线程、进程和协程,用于优化IO操作和响应速度。`threading`模块支持线程,`multiprocessing`处理进程,而`asyncio`则用于协程。线程通过Lock和Condition Objects同步,进程使用Queue和Pipe通信。协程利用异步事件循环避免上下文切换。了解并发模型及同步技术是提升Python应用性能的关键。
37 5
|
9天前
|
安全 Java 程序员
惊呆了!Java多线程里的“synchronized”竟然这么神奇!
【6月更文挑战第20天】Java的`synchronized`关键字是解决线程安全的关键,它确保同一时间只有一个线程访问同步代码。在案例中,`Counter`类的`increment`方法如果不加同步,可能会导致竞态条件。通过使用`synchronized`方法或语句块,可以防止这种情况,确保线程安全。虽然同步会带来性能影响,但它是构建并发应用的重要工具,平衡同步与性能是使用时需考虑的。了解并恰当使用`synchronized`,能有效应对多线程挑战。
|
15天前
|
Java Python
Python中的并发编程(3)线程池、锁
Python中的并发编程(3)线程池、锁
|
14天前
|
存储 JSON 算法
Python中的并发编程(4)多线程发送网络请求
Python中的并发编程(4)多线程发送网络请求
|
3天前
|
缓存 并行计算 安全
【并发编程系列一】并发编年史:线程的双刃剑——从优势到风险的全面解析
【并发编程系列一】并发编年史:线程的双刃剑——从优势到风险的全面解析

热门文章

最新文章