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

Posted 键盘AQ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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.背景知识

多线程三大特性:

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

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

有序性: join方法  wait和notify

 

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能够在修改结束后会立刻将其刷新到主内存中,所有的程序就是这样




以上是关于JAVA从java线程来看java内存模型的主要内容,如果未能解决你的问题,请参考以下文章

(Java多线程系列七)Java内存模型和线程的三大特性

java面试-Java内存模型(JMM)

Java内存模型之可见性问题

java线程基础梳理

volatile学习

Java内存模型