【JAVA】从java线程来看java内存模型

前言

本来是想写两个线程,线程1输出1-98的奇数,线程2输出1-98的偶数,交替执行,在测试的时候发现线程安全问题,之后又引入到java内存模型,下面是几个demo。

1.版本1

//to print 1 ,3, 5...by thread1, print 2,4,6,8,10... by thread2 by turns
public class Circle {

  public static boolean flag = true;

  public static void main(String[] args) {
    Thread t1 = new Thread(new Runnable() {
      int i = 1;

      @Override
      public void run() {
        while (i < 99) {
          if (flag == true) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            i = i + 2;
            flag = false;
          }
        }
      }
    });

    Thread t2 = new Thread(new Runnable() {
      int i = 2;

      @Override
      public void run() {
        while (i < 99) {
          if (flag == false) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            i = i + 2;
            flag = true;
          }
        }
      }
    });

    t1.start();
    t2.start();

  }
}

版本1很多次结果输出正常,偶尔会出现线程停留在中间某步不继续执行。

2.版本2, 在版本1的基础上给其中一个线程加上sleep时间

//to print 1 ,3, 5...by thread1, print 2,4,6,8,10... by thread2 by turns
public class Circle {

  public static boolean flag = true;

  public static void main(String[] args) {
    Thread t1 = new Thread(new Runnable() {
      int i = 1;

      @Override
      public void run() {
        while (i < 99) {
          try {
            Thread.sleep(3);
          } catch (Exception e) {
            e.printStackTrace();
          }
          if (flag == true) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            i = i + 2;
            flag = false;
          }

        }
      }
    });

    Thread t2 = new Thread(new Runnable() {
      int i = 2;

      @Override
      public void run() {
        while (i < 99) {
          if (flag == false) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            i = i + 2;
            flag = true;
          }
        }
      }
    });

    t1.start();
    t2.start();

  }
}

结果随机,可能停留在Thread-0: 1 ,也可能停留在中间某步。

3.版本3 给两个线程都sleep一段时间

//to print 1 ,3, 5...by thread1, print 2,4,6,8,10... by thread2 by turns
public class Circle {

  public static boolean flag = true;

  public static void main(String[] args) {
    Thread t1 = new Thread(new Runnable() {
      int i = 1;

      @Override
      public void run() {
        while (i < 99) {
          try {
            Thread.sleep(3);
          } catch (Exception e) {
            e.printStackTrace();
          }
          if (flag == true) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            i = i + 2;
            flag = false;
          }

        }
      }
    });

    Thread t2 = new Thread(new Runnable() {
      int i = 2;

      @Override
      public void run() {
        while (i < 99) {
          try {
            Thread.sleep(3);
          } catch (Exception e) {
            e.printStackTrace();
          }
          if (flag == false) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            i = i + 2;
            flag = true;
          }
        }
      }
    });

    t1.start();
    t2.start();

  }
}

结果正常(在我的机器上没演示出不正常的现象,理论上是会出现的)

4.再看版本4  在版本2的基础上加上volatile

//to print 1 ,3, 5...by thread1, print 2,4,6...by thread2 by turns
public class Circle {

  public static volatile boolean flag = true;

  public static void main(String[] args) {
    Thread t1 = new Thread(new Runnable() {
      int i = 1;

      @Override
      public void run() {
        while (i < 99) {
          try {
            Thread.sleep(3);
          } catch (Exception e) {
            e.printStackTrace();
          }
          if (flag == true) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            i = i + 2;
            flag = false;
          }

        }
      }
    });

    Thread t2 = new Thread(new Runnable() {
      int i = 2;

      @Override
      public void run() {
        while (i < 99) {
          if (flag == false) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            i = i + 2;
            flag = true;
          }
        }
      }
    });

    t1.start();
    t2.start();

  }
}

结果执行正常,这是就涉及java volatile关键字了,volatile是保证线程之间的可见性,是保证全局变量flag对线程1和线程2的可见性。

分析;

1.背景知识

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px ".PingFang SC" }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Helvetica Neue" }
span.s1 { font: 12.0px "Helvetica Neue" }
span.s2 { font: 12.0px ".PingFang SC" }

多线程三大特性:

原子性:保障线程安全问题

可见性:java内存模型 — 不可见

有序性: join方法  wait和notify

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px ".PingFang SC" }
span.s1 { font: 12.0px "Helvetica Neue" }

java内存模型:

主内存(存放共享的全局变量)

私有本地内存(本地线程私有变量 )

本地内存存放共享数据副本

2.本案例中,两个线程线程1和线程2共享一个全局变量flag,这两个线程的内存称为私有本地内存,主内存main方法称为主内存。

刚开始,主内存flag为true,主内存通知线程1和线程2的本地内存flag的值,两个线程获取到flag的值为true.

之后,两个线程进行判断,线程1满足条件,线程2不满足条件,线程1执行,执行完后设定flag=false.

再之后,线程1刷新flag的值为false到主内存,假定时间为(0.003s)

之后,主内存通知线程2拿到flag的值,flag拿到为flase,开始执行,执行完毕设定flag为true

在之后,线程2刷新flag的值为false到主内存,假定时间为(0.003s)

再之后,主内存通知线程1拿到flag的值,依次执行.

重点是线程刷新flag最新的值到主内存的时间点,并不确定,借用别人一篇博客的摘录,这本书我没看过(打脸)

在多线程的环境下,如果某个线程首次读取共享变量,则首先到主内存中获取该变量,然后存入工作内存中,以后只需要在工作内存中读取该变量即可。同样如果对该变量执行了修改的操作,则先将新值写入工作内存中,然后再刷新至主内存中。但是什么时候最新的值会被刷新至主内存中是不太确定的,这也就解释了为什么VolatileFoo中的Reader线程始终无法获取到init_value最新的变化。
· 使用关键字volatile,当一个变量被volatile关键字修饰时,对于共享资源的读操作会直接在主内存中进行(当然也会缓存到工作内存中,当其他线程对该共享资源进行了修改,则会导致当前线程在工作内存中的共享资源失效,所以必须从主内存中再次获取),对于共享资源的写操作当然是先要修改工作内存,但是修改结束后会立刻将其刷新到主内存中。
· 通过synchronized关键字能够保证可见性,synchronized关键字能够保证同一时刻只有一个线程获得锁,然后执行同步方法,并且还会确保在锁释放之前,会将对变量的修改刷新到主内存当中。
· 通过JUC提供的显式锁Lock也能够保证可见性,Lock的lock方法能够保证在同一时 刻只有一个线程获得锁然后执行同步方法,并且会确保在锁释放(Lock的unlock方法)之前会将对变量的修改刷新到主内存当中。

  摘自:《Java高并发编程详解:多线程与架构设计》 — 汪文君

所以说,使用volatile能够在修改结束后会立刻将其刷新到主内存中,所有的程序就是这样

原文地址:https://www.cnblogs.com/jianpanaq/p/10264845.html

时间: 2024-08-04 04:28:39

【JAVA】从java线程来看java内存模型的相关文章

Java锁(一)之内存模型

想要了解Java锁机制.引发的线程安全问题以及数据一致性问题,有必要了解内存模型,机理机制了解清楚了,这些问题也就应声而解了. 一.主内存和工作内存 Java内存模型分为主内存和工作内存,所有的变量都存储在主内存中.每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量.不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要主内存来完成. 二.线程.工作内存和主内存 下面是

Java(1):多线程内存模型和状态切换

线程的内存模型 32位操作系统的寻址空间为2的32次方,也就是4GB的寻址空间:系统在这4GB的空间里划分出1GB的空间给系统专用,称作内核空间,具有最高权限:剩下3GB的空间为用户空间(一般JVM的可用内存最大只能是2GB),只能访问当前线程划分的内存地址.用户线程需要访问硬件资源的时候需要委托内核线程进行访问,这就涉及到CPU上下文在用户模式和内核模式的切换.因此在使用线程或者进程的时候需要尽量避免不必要的用户模式和内核模式的切换. 进程是资源管理的最小单位,线程是CPU调度的最小单位.线程

JAVA高级篇(二、JVM内存模型、内存管理之第二篇)

本文转自https://zhuanlan.zhihu.com/p/25713880. JVM的基础概念 JVM的中文名称叫Java虚拟机,它是由软件技术模拟出计算机运行的一个虚拟的计算机. JVM也充当着一个翻译官的角色,我们编写出的Java程序,是不能够被操作系统所直接识别的,这时候JVM的作用就体现出来了,它负责把我们的程序翻译给系统"听",告诉它我们的程序需要做什么操作. 我们都知道Java的程序需要经过编译后,产生.Class文件,JVM才能识别并运行它,JVM针对每个操作系统

高并发编程之线程安全与内存模型

微信公众号:Java修炼指南关注可与各位开发者共同探讨学习经验,以及进阶经验.如果有什么问题或建议,请在公众号留言.博客:https://home.cnblogs.com/u/wuyx/ 前几期简单介绍了一些线程方面的基础知识,以及一些线程的一些基础用法(想看往期文章的小伙伴可以直接拉到文章最下方飞速前往).本文通过java内存模型来介绍线程之间不可见的原因. 本期精彩原子性有序性指令重排序可见性Happen-Before规则 原子性 原子性对于我们开发者来说应该算是比较熟悉的了,通俗点说就是执

(转)【Java线程】Java内存模型总结

Java的并发采用的是共享内存模型(而非消息传递模型),线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现 同步是显式进行的.程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行. 1.多线程通信 1.1 内存模型 Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见. 从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程

Java内存模型与线程 深入理解Java虚拟机总结

在许多情况下,让计算机同时去做几件事情,不仅是因为计算机的运算能力强大了,还有一个很重要的原因是计算机的运算速度与它的存储和通信子系统速度的差距太大, 大量的时间都花费在磁盘I/O.网络通信或者数据库访问上. 如果不希望处理器在大部分时间里都处于等待其他资源的状态,就必须使用一些手段去把处理器的运算能力 " 压榨 " 出来, 否则就会造成很大的浪费,而计算机同时处理几项任务则是最容易想到.也被证明是非常有效的 " 压榨 " 手段. 除了充分利用计算机处理器的能力外,

java内存模型与线程(转) good

java内存模型与线程 参考 http://baike.baidu.com/view/8657411.htm http://developer.51cto.com/art/201309/410971_all.htm http://www.cnblogs.com/skywang12345/p/3447546.html 计算机的CPU计算能力超强,其计算速度与 内存等存储 和通讯子系统的速度相比快了几个数量级, 数据加载到内存中后,cpu处理器运算处理时,大部分时间花在等待获取去获取磁盘IO.网络通

深入理解Java内存模型(1 ) -- 基础(转载)

原文地址:http://www.infoq.com/cn/articles/java-memory-model-1 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程之间没有公共状态,线

深入理解Java内存模型之系列篇[转]

原文链接:http://blog.csdn.net/ccit0519/article/details/11241403 深入理解Java内存模型(一)——基础 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. see:命令式编程.函数式编程 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存