java多线程之volatile讲解

java多线程之volatile讲解

?? 最近一直在看多线程的一些知识,看了一些书和一些博客,收获还是挺多的,最近看了《java并发编程的艺术》这本书感觉收获很大也推荐给各位,同时也结合以前看的博客就好好的总结一下自己所学的东西吧,有不足的地方欢迎各位指正,这篇文章主要是讲volatile关键字的知识。

volatile的特性

  • 可见性:volatile在多线程中能够保证共享变量的“可见性”,简单的说就是当一个线程修改了volatile变量的时候,java线程内存模型能够确保所有的线程看到的这个变量的值是一致的。
  • 防止指令重排序

java内存模型

  • 在学习volatile的知识之前我们先来简单了解下java内存模型(JMM)引用一张网上很经典的表示java内存模型的图

  • 大概解释下这个图的意思

    • 在多个线程运行的时候每一个线程(Thread)都有一个属于自己的内存空间
    • 多个线程共同使用一个主存
    • 每个线程在对数据进行修改之前都会先在主存里面获取相关数据,然后在自己的工作内存里面对数据进行操作。

      可见性

      关键的地方就来了,因为每个线程然都是在自己的内存里进行操作,然而每个线程的工作内存之间都是相互不可见的,所以对共享变量的修改并不会马上被其他线程看到,所以就会造成多个线程操作同一个数据但是最后结果并不是我们期望的结果。当线程1去首先从主存中加载一个volatile变量到自己的工作内,然后对这个volatile变量进行写操作,写入操作结束之后,volatile变量的最新值会立马刷新到主内存,同时其他线程中的这个volatile变量会立马失效,会被强迫从主内存中重新读取volatile变量的最新值,这就是volatile变量的可见性实现的过程。同时这也可以看做是一个线程和其他线程通信的一个过程。

      防止指令重排序

      简单解释下指令重排序,重排序指的是编译器和处理器为了优化程序的性能会对指令序列进行重新排序的一种手段。在单线程的程序里,指令的重排序会保证执行结果的正确性,但是在多线程中指令的重排序对程序的执行结果的正确性就得不到保障(指令重排序的一些规则各位可以去查阅一下,这里不赘述)。volatile变量防止指令重排序请先看下面的内存屏障介绍。(其实synchronized也对指令的重排序有一些限制,在后面我会写一篇讲解synchronized的博文会详细讲解一下,欢迎各位阅读)

内存屏障

JMM把内存屏障分为四类(摘自Java并发编程艺术)

| 屏障类型 | 指令示例 | 说明 |

| ----------------- |:-------------:| -----:|

| LoadLoad Barriers | Loadl; LoadLoad; Load2 | 确保Loadl数据的装载先于Load2及所有后续装载指令 |

| StoreStore Barriers | Storel; StoreStore; Store2 | 确保Store1数据对其他处理器可见(刷新到内存)先于Store2及所有后续存储指令的存储|

|LoadStore Barriers | Loadl; LoadStore; Store2 |确保Loadl数据装载先于Store2及所有后续的存储指令刷新到内存|

|StoreLoad Barriers |Storel; StoreLoad; Load2 |确保Storel数据对其他处理器变得可见(指刷新到内存)先于Load2及所有后续装载指令的装载。StoreLoad Barriers会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令|

  • volatile变量基于保守策略的JMM内存屏障插入策略

    • 在每个volatile写操作的前面插入一个StoreStore屏障。
    • 在每个volatile写操作的后面插入一个StoreLoad屏障。
    • 在每个volatile读操作的后面插入一个LoadLoad屏障。
    • 在每个volatile读操作的后面插入一个LoadStore屏障。

看完这个相信各位对volatile防止指令重排序就有一个比较清楚的认识了,解释一下上面的四条策略,

  • 在volatile变量的写操作前面的其他写操作会在volatile变量写前面执行(提前刷新到主存,对其他线程可见)
  • volatile变量的写会比后面的其他读写操作先进行
  • volatile变量读操作前面的读操作会在volatile变量读操作以前进行
  • volatile变量读操作后面的其他写操作会在volatile变量读操作以后进行

    volatile的应用

    很多博客中基本上都说了volatile变量一般运用于不依附当前值的操作,比如自增,我的理解是这样的,如果volatile变量进行依附当前操作的值的运算,那么就会涉及到读volatile变量和写volatile变量这两个操作,volatile变量的读操作(从主内存读取到线程的工作内存)和写操作(将变量写到主内存中去)时,这两个组合起来的操作就是一个非原子性的操作,所以这种情况下使用volatile关键字就不合适,对于基本变量的一些非原子性操作(如自增)可以考虑使用java.util.concurrent.atomic包下的一些类,或者使用锁来进行。volatile变量一般用作比如一个标志变量这种单个读写的操作。

    附上个人觉得volatile变量应用的一个讲得比较好的博客(https://www.ibm.com/developerworks/cn/java/j-jtp06197.html

    同时各位也可以去看一下另外一篇讲解volatile的博文,同样比较棒(http://kwsir.cn/2017/10/12/Java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B-volatile%E5%8F%AF%E8%A7%81%E6%80%A7%E7%9A%84%E4%BB%8B%E7%BB%8D/)。

写在最后

鉴于本人水平有限,所以如果文章中有不对的地方,十分欢迎各位在评论留言指点,或者发送到本人的qq邮箱[email protected]通知一下本人。谢谢大家。

原文地址:https://www.cnblogs.com/NewHongjay/p/9047986.html

时间: 2024-10-10 20:50:34

java多线程之volatile讲解的相关文章

JAVA多线程之volatile 与 synchronized 的比较

一,volatile关键字的可见性 要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下: 从图中可以看出: ①每个线程都有一个自己的本地内存空间--线程栈空间???线程执行时,先把变量从主内存读取到线程自己的本地内存空间,然后再对该变量进行操作 ②对该变量操作完后,在某个时间再把变量刷新回主内存 关于JAVA内存模型,更详细的可参考: 深入理解Java内存模型(一)——基础 因此,就存在内存可见性问题,看一个示例程序:(摘自书上) 1 public c

java多线程之volatile关键字

在java线程并发处理中,关键字volatile的主要作用是使变量在多个线程间可见.那么volatile到底该怎么用了?我们首先来看一段代码: public class MyThread1 implements Runnable { private boolean istag = true; public boolean isIstag() { return istag; } public void setIstag(boolean istag) { this.istag = istag; }

JAVA多线程之wait/notify

本文主要学习JAVA多线程中的 wait()方法 与 notify()/notifyAll()方法的用法. ①wait() 与 notify/notifyAll 方法必须在同步代码块中使用 ②wait() 与  notify/notifyAll() 的执行过程 ③中断 调用wait()方法进入等待队列的 线程 ④notify 通知的顺序不能错 ⑤多线程中测试某个条件的变化用 if 还是用 while? ①wait() 与 notify/notifyAll 方法必须在同步代码块中使用 wait()

Java多线程之wait(),notify(),notifyAll()

在多线程的情况下,因为同一进程的多个线程共享同一片存储空间,在带来方便的同一时候,也带来了訪问冲突这个严重的问题.Java语言提供了专门机制以解决这样的冲突,有效避免了同一个数据对象被多个线程同一时候訪问. wait与notify是java同步机制中重要的组成部分.结合与synchronizedkeyword使用,能够建立非常多优秀的同步模型. synchronized(this){ }等价于publicsynchronized void method(){.....} 同步分为类级别和对象级别

java多线程之ThreadLocal

ThreadLocal保证数据同步 package Thread.Common; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; class Accessor implements Runnable { private final int id; pub

java多线程之Future和FutureTask

Executor框架使用Runnable 作为其基本的任务表示形式.Runnable是一种有局限性的抽象,然后可以写入日志,或者共享的数据结构,但是他不能返回一个值. 许多任务实际上都是存在延迟计算的:执行数据库查询,从网络上获取资源,或者某个复杂耗时的计算.对于这种任务,Callable是一个更好的抽象,他能返回一个值,并可能抛出一个异常.Future表示一个任务的周期,并提供了相应的方法来判断是否已经完成或者取消,以及获取任务的结果和取消任务. public interface Callab

Java多线程之Wait()和Notify()

1.Wait()和Notify.NotifyAll都是Object的方法 2.多线程的协作是通过控制同一个对象的Wait()和Notify()完成 3.当调用Wait()方法时,当前线程进入阻塞状态,直到有另一线程调用了该对象的Notify()方法 package Thread.Wait; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.con

Java多线程之notifyAll的作用域

notifyAll()因某个特定锁而被调用时,只有等待这个锁的任务才会被唤醒. package Thread.Wait; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class Blocker { synchronized void waitingCall() { try

Java多线程之join

将另外一个线程join到当前线程,则需要等到join进来的线程执行完才会继续执行当前线程. package Thread.join; class Sleeper extends Thread { private int duration; public Sleeper(String name, int sleepTime) { super(name); duration = sleepTime; start(); } public void run() { try { sleep(duratio