美文网首页JavaWeb
JUC-001-volatile与内存可见性

JUC-001-volatile与内存可见性

作者: 53b3f4658edc | 来源:发表于2017-11-19 14:59 被阅读10次

JUC简介

  • 在Java 5.0 提供了java.util.concurrent(简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的Collection 实现等。

内存可见性(Memory Visibility)

  • 内存可见性(Memory Visibility)是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。
  • 可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。
  • 我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的volatile 变量。
微信公众号:JavaWeb架构师

测试代码

package top.itcourse._volatile;

import org.junit.Test;
import org.omg.CORBA.FloatSeqHelper;

/*
 * 内存可见性(Memory Visibility):
 *      - 内存可见性(Memory Visibility)是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象
 *          状态后,其他线程能够看到发生的状态变化。
 *      - 可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根
 *          本不可能的事情。
 *      - 我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的volatile 变量。 
 * 
 * 说明:
 *  线程对象中的属性是放在主存中的,线程修改/读取的属性是在自己线程的缓存中,当修改完毕之后再把值
 *  放回主存(不同的线程中去读取/修改的属性值,都是主存中的值)
 */


/*
 * 下面我们实现:
 *  线程一、二都读取到flag的初始值,然后在线程一的run方法中改变flag的值,但是线程二的flag值没有同步更新
 */
public class TestMemVisi {
    @Test
    public void testMemVisi() throws InterruptedException {
        // 2.新建实现了Runnable的对象
        MemVisi mv1 = new MemVisi();
        // 3.传递到Thread对象中start
        new Thread(mv1).start();
        
        while(true) {
            // 线程二:一直是false(因为线程一sleep的时候,它进来了,又因为是while循环,使得线程二根本没有机会再次从主存中去读取新值(可以说明
            //  没有线程间没有同步的通知、更新))
            if( mv1.isflag() ) {
                System.out.println("我在主线程打印,flag是true");
            }
            
            // Thread.sleep(50); // 加上这句话,就可以有机会去主存中读取新值
        }
    }
}

// 1.实现Runnable
class MemVisi implements Runnable {
    
    private boolean flag = false;
    
    public boolean isflag() {
        return flag;
    }

    public void setflag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        // 让线程二先进去判断(while判断)       
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
        }
        
        // 线程一:     
        flag = true;
        System.out.println("flag: " + flag);
    }
}

运行结果:
flag: true


volatile 关键字

  • Java提供了一种稍弱的同步机制,即volatile 变量,用来确保将变量的更新操作通知到其他线程。可以将volatile 看做一个轻量级的锁,但是又与锁有些不同:

    • 对于多线程,不是一种互斥关系
    • 不能保证变量状态的“原子性操作”
  • 计算机在运行程序时,每条指令都是在CPU中执行的,在执行过程中势必会涉及到数据的读写。我们知道程序运行的数据是存储在主存中,这时就会有一个问题,读写主存中的数据没有CPU中执行指令的速度快,如果任何的交互都需要与主存打交道则会大大影响效率,所以就有了CPU高速缓存。CPU高速缓存为某个CPU独有,只与在该CPU运行的线程有关。

  • 有了CPU高速缓存虽然解决了效率问题,但是它会带来一个新的问题:数据一致性。在程序运行中,会将运行所需要的数据复制一份到CPU高速缓存中,在进行运算时CPU不再也主存打交道,而是直接从高速缓存中读写数据,只有当运行结束后才会将数据刷新到主存中。

  • 解决方案:

    • 加锁
      • 独占,只能有一个运行,其它都得阻塞
    • 缓存一致性协议(volatitle)
      • 缓存一致性协议(MESI协议)它确保每个缓存中使用的共享变量的副本是一致的。其核心思想如下:当某个CPU在写数据时,如果发现操作的变量是共享变量,则会通知其他CPU告知该变量的缓存行是无效的,因此其他CPU在读取该变量时,发现其无效会重新从主存中加载数据。


        微信公众号:JavaWeb架构师

测试代码

package top.itcourse._volatile;

import org.junit.Test;

/*
 * volatile关键字:
 * Java提供了一种稍弱的同步机制,即volatile 变量,用来确保将变量的更新操作通知到其他线程。可以将volatile 看做一个轻量级的锁,但是又与锁有些不同:
 *      - 对于多线程,不是一种互斥关系(同步通知,所以效率会高一些)
 *      - 不能保证变量状态的“原子性操作”(下节讲)
 * 
 * 作用:多个线程操作共享数据时,保证内存中的数据是可见的
 */

/*
 * 解决同步通知、更新的问题:
 *  1.使用锁(效率低)
 *  2.
 */
public class TestVolatile {
    @Test
    public void testMemVisi() throws InterruptedException {
        // 2.新建实现了Runnable的对象
        MemVisia mv1 = new MemVisia();
        // 3.传递到Thread对象中start
        new Thread(mv1).start();
        
        while(true) {
            // 使用synchronized,可以解决,会去刷新缓存           
//          synchronized(mv1) {
//              if( mv1.isflag() ) {
//                  System.out.println("我在主线程打印,flag是true");
//                  break;
//              }
//          }
            
            if( mv1.isflag() ) {
                System.out.println("我在主线程打印,flag是true");
                break;
            }
            
        }
    }
}

// 1.实现Runnable
class MemVisia implements Runnable {
    
    // 使用volatile关键字,就可以解决了
    private volatile boolean flag = false;
    
    public boolean isflag() {
        return flag;
    }

    public void setflag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        // 让线程二先进去判断(while判断)       
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
        }
        
        // 线程一:     
        flag = true;
        System.out.println("flag: " + flag);
    }
}

结果:
我在主线程打印,flag是true
flag: true


其它



源码下载:

关注下方微信公众号,
回复:
JUC.code
完整教程PDF版本下载

相关文章

  • JUC-001-volatile与内存可见性

    JUC简介 在Java 5.0 提供了java.util.concurrent(简称JUC )包,在此包中增加了在...

  • 多线程 | Volatile到底有什么用?

    Volatile的作用: 保持内存可见性.内存可见性:多个线程操作同一个变量,可确保写线程更新变量,其他读线程可以...

  • 深入浅出 Java 并发编程 (2)

    本文目录 Java 内存模型与可见性 指令重排序 使用 volatile 关键字保证可见性 使用 synchron...

  • volatile

    目标 1、volatile如何保证内存可见性2、volatile如何禁止指令重排序3、内存屏障4、内存可见性5、关...

  • 对变量加锁后是否还需要使用volatile

    大家都知道volatile保证了变量在线程间的可见性(主内存与CPU缓存(线程内存)间)。Lock与synchro...

  • volatile

    volatile 内存唯一 可见性

  • java初入多线程5

    volatile 与java内存模型(JMM) java的内存模型都是围绕着原子性、有序性、还有可见性来展开的。 ...

  • Volatile原理总结

    内存可见性 内存可见性相关概念:线程对共享变量修改的可见性。当一个线程修改了共享变量的值,其他线程能够立刻得知这个...

  • 面试题收集

    Synchronized关键字和ReentrantLock的对比相同点:互斥性、内存可见性、可重入 不同点: ①R...

  • java内存结构和java内存模型

    java内存模型:与多线程JMM,就是线程可见性有关java内存结构:JVM虚拟机存储空间 class文件被类加载...

网友评论

    本文标题:JUC-001-volatile与内存可见性

    本文链接:https://www.haomeiwen.com/subject/gpzuvxtx.html