• 细说Java多线程之内存可见性


    synchronized实现的可见性

    JMM (java memory model) 关于synchronized的两条规定

    线程解锁前,必须把共享变量的最新值刷新到主内存中

    线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时,需要从主内存中重新读取最新的值(注意,加锁与解锁需要是同一把锁

    线程解锁前对共享变量的修改在下次加锁是对其他线程可见

    线程执行互斥代码的过程

    1.获得互斥锁

    2.清空工作内存

    3.从主内存拷贝变量的最新副本到工作内存

    4.执行代码

    5.将更改后的共享变量的值刷新到主内存

    6.释放互斥锁


    重排序

    代码书写的顺序与实际执行的顺序不同,指令重排序是编译器或处理器为了提高程序性能而做的优化

    1.编译器优化的重排序(编译器优化)

    2.指令集并行重排序 (处理器优化)

    3.内存系统的重排序 (处理器优化)


    as-if-serial

    无论如何重排序,程序执行的结果应该与代码顺序执行的结果一致(Java编译器、运行时和处理器都会保证Java在单线程下遵循as-if-serial 语义)


    导致共享变量在线程间不可见的原因                     synchronized解决方案:

    1.线程的交叉执行                                                                 ---------------->原子性

    2.重拍结合线程交叉执行                                                     ---------------->原子性

    3.共享变量更新后的值没有在工作内存与主内存间及时更新         ---->可见性



    volatile 实现可见性

    volatile 关键字

    能够保证volatile变量的可见性

    不能保证volatile变量符合操作的原子性

    volatile如何实现内存可见性:

    深入来说:通过加入内存屏障和禁止重排序优化来实现的。

    对volatile 变量执行写操作时,会在写操作后加入一条store屏障指令

    对volatile变量执行读操作时,会在读操作前加入一条load屏障指令

    通俗地讲:volatile变量发生变化时,又会强迫线程将最新的值刷新到主内存。这样任何时刻,不同的线程总能看到该变量的最新值

    线程写 volatile变量的过程

    1.改变线程工作内存中volatile变量副本的值

    2.将改变后的副本的值从工作内存中刷新到主内存

    线程读 volatile变量的过程

    1.从主内存中读取volatile变量的最新值到线程的工作内存中

    2.从工作内存中读取volatile变量的副本

    volatile 不能保证volatile 变量渡河操作的原子性



    public class VolatileDemo {


    volatile int number = 0;


    public int getNumber(){
    return this.number;
    }
    public void increase(){
    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    this.number++;
    }


    public static void main(String[] args) {

    VolatileDemo volatileDemo = new VolatileDemo();
    for(int i=0;i<500;i++){
    new Thread(new Runnable() {

    @Override
    public void run() {
    // TODO Auto-generated method stub
    volatileDemo.increase();
    }
    }).start();
    }
    //如果还有子线程在运行,主线程就让出CPU资源
    //知道所有的子线程都运行完了,在运行主线程继续往下执行
    while(Thread.activeCount()>1){
    Thread.yield();
    }
    System.out.println("number:"+volatileDemo.getNumber());



    }


    }



    解决方案一

      int number = 0; 去掉volatile

    public int getNumber(){
    return this.number;
    }
    public  void increase(){
    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    synchronized(this){
    this.number++;
    }

    }

    解决方案二

    用ReentrantLock实现number变量在线程中的原子性

                    Lock lock = new ReentrantLock();

    //加锁和开锁块,就相当于synchronized
    lock.lock();

    try {
    this.number++;
    } finally  {
    lock.unlock();
    }

  • 相关阅读:
    mysql------Windows7 64bit安装教程------下载mysql
    [org.springframework.context.annotation.ComponentScanBeanDefinitionParser] are only available on JDK 1.5 and higher 问题--MyEclipse设置JDK版本
    码云Gitee上新建项目教程
    远程桌面无法复制粘贴
    Submine Text3格式化HTML/CSS/JS代码
    FTP上传文件,报错java.net.SocketException: Software caused connection abort: recv failed
    在MyEclipse使用Git新建分支,并上传分支---图文教程
    使用Git Bash上传代码到新的分支
    使用Git Bash从Git上下载代码到本地以及上传代码到码云Git
    安装Git Bash图文教程
  • 原文地址:https://www.cnblogs.com/CCCrunner/p/6444551.html
Copyright © 2020-2023  润新知