wangzhenhui1991 / Notes

3 stars 0 forks source link

Java 多线程:volatile 关键字 #13

Open wangzhenhui1991 opened 7 years ago

wangzhenhui1991 commented 7 years ago

概念

在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。它在某些情况下比synchronized的开销更小,本文将深入分析在硬件层面上Inter处理器是如何实现Volatile的,通过深入分析能帮助我们正确的使用Volatile变量。

共享变量定义:在多个线程之间能够被共享的变量被称为共享变量。共享变量包括所有的实例变量,静态变量和数组元素。他们都被存放在堆内存中,Volatile只作用于共享变量。

Volatile的官方定义

Java语言规范第三版中对volatile的定义如下: java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。

为什么要使用Volatile

Volatile变量修饰符如果使用恰当的话,它比synchronized的使用和执行成本会更低,因为它不会引起线程上下文的切换和调度。

以上参考 聊聊并发(一)深入分析Volatile的实现原理

volatile 也是 多线程的解决方案之一。 volatile 能够保证 可见性,但是不能保证原子性。它只能作用于变量,不能作用于方法

关于什么时候使用 volatile,一般是用来当做标记来使用。比如说,当shutdown() 方法被调用的时候,所有的 doWork() 方法都会停下来。

实现原理-详细-涉及到汇编代码-没必要

首先,需要了解一个概念:缓存行 - 缓存中可以分配的最小存储单位。当我们对 volatile 变量进行写操作的时候,把 Java 代码翻译到汇编代码的时候,会多出一个 Lock 前缀的指令,它在多核处理下会引发两个事情:

再细节一点说:

每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。

volatile 的使用

使用volatile 有两点需要注意的地方:

运算结果并不依赖于当前值,或者能确保只有单一的线程能够修改变量的值。 变量不需要和其他的状态变量共同参与不变约束 对于第一点的理解:


public class Test {
    public static volatile int i = 0;
    public static void main(String args[]){

        new Thread(new Runnable(){
            public void run(){
                for(int j = 0; j < 10000; j++)
                    i++;
                System.out.println("Thread1 end...");
            }
        }).start();

        new Thread(new Runnable(){
            public void run(){
                for(int j = 0; j < 10000; j++)
                    i++;
                System.out.println("Thread2 end...");
            }
        }).start(); 

        i++;

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("i = " + i);
    }
}

对于第二点的理解:


private Date start;      
private Date end;      

public void setInterval(Date newStart, Date newEnd) {      
    // 检查start<end是否成立, 在给start赋值之前不变式是有效的      
    start = newStart;      

    // 但是如果另外的线程在给start赋值之后给end赋值之前时检查start<end, 该不变式是无效的      

    end = newEnd;      
    // 给end赋值之后start<end不变式重新变为有效      
}