volatile

volatile

  1. 线程间的可见性
  2. 防止指令重排

线程间的可见性

先了解下java的内存模型主内存和工作内存

java的内存模型主要目标是定义程序中各个变量的访问规则,即虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。而这些变量包括实例字段、静态字段和构成数组对象的元素。————摘自深入理解JVM第十二章

如上图所示,每个线程都有自己独立的工作内存而在这些工作内存中使用的变量需要从主内存中读取或存储到主内存中。

好,看下下面这段代码(——来自Java多线程核心技术)

public class Run {
public static void main(String[] args) {
    try {
        RunThread runThread = new RunThread();
        runThread.start();
        Thread.sleep(1000);
        runThread.setRunning(false);
        System.out.println("已经赋值为false");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
}

class RunThread extends Thread {
private boolean isRunning = true;

public boolean isRunning() {
    return isRunning;
}

public void setRunning(boolean isRunning) {
    this.isRunning = isRunning;
}

@Override
public void run() {
    System.out.println("进入run了");
    while (isRunning == true) {

    }
    System.out.println("退出了!!!!");
}
}

陷入死循环了….. why?

有几个线程?

2个 一个main,一个thread-0

答案:应该是3个还有个守护线程

 //在Thread.sleep(1000);下面加上这两句
 System.out.println(Thread.activeCount());
 Thread.currentThread().getThreadGroup().list();


lock(锁定):作用于主内存变量,把一个变量标示为一条线程独占的状态

unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定

read(读取):作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用

load(载入):作用于工作内存的变量,把read操作从主存中得到的变量值放入工作内存的变量副本中

use(使用):作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作

assign(赋值):作用于工作内存的变量,把一个从执行引擎接收到的值赋给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作

store(存储):作用于工作内存的变量,把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用

write(写入):作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中

分析


上图表示是线程从主内存获取变量到工作内存


上图表示main将isRunning变量修改后更新到主内存

又上面两张图可知道虽然main线程更新了isRunning变量到主内存但是thread-0读取的编程是自己工作内存中的信息isRunning=true,所以会一直陷入循环。


解决方案

使用volatile修饰isRunning变量

 private volatile boolean isRunning = true;


使用System.out.println()

while (isRunning == true) {
        System.out.println("在循环中!!");
    }

why?

下面是println方法的源码,使用了synchronized块
/**
 * Prints a String and then terminate the line.  This method behaves as
 * though it invokes <code>{@link #print(String)}</code> and then
 * <code>{@link #println()}</code>.
 *
 * @param x  The <code>String</code> to be printed.
 */
public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

百度了下再看了下深入理解JVM发现做了锁优化

锁粗化

原则上我们在编写代码的时候,总是推荐同步块的作用范围限制得尽量小————只在共享数据的实际作域中才进行同步,这样是为了使用需要同步的操作数量尽可能变小,如果存在锁竞争,那等待锁的线程也能尽快拿到锁。

大部分情况下,上面的原则都是正确的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中,那即使没有线程竞争,频繁的进行互斥同步操作也会导致不必要的性能损耗。如果虚拟机探测到有这样一串零碎的操作对同一个对象锁,将会把锁同步范围扩展(粗化)到整个操作序列的外部。

–深入理解JVM,13章线程安全与锁优化

所以应该会变成这样

synchronized(this){
    while(isRunning == true){
    }
}

synchronized规定,线程在加锁时,先清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。

为什么有这么多在循环中!!! 应该是Thread.sleep(1000)的原因主线程先休眠1秒再配置isRunning


使用Thread.sleep()

while (isRunning == true) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

Thread.sleep()是阻塞线程并不释放锁,而是让出cpu调度。 让出cpu调度后下次执行会刷新工作内存。

所以什么是可见性?

可见性指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。


防止指令重排

指令重排

编译器为了提高程序的执行效率会按照一定的规则允许指令优化,不影响单线程程序执行结果,但是多线程就会影响程序结果。

参考深入理解JAVA虚拟机
Java多线程编程核心技术
https://www.cnblogs.com/LQBlog/p/8718735.html

如果有什么错误欢迎指出来,感谢感谢!才学疏浅望谅解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章