欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java并发(一):Java内存模型干货总结

程序员文章站 2022-11-22 21:30:30
synchronized是一个重量级的锁,volatile通常被比喻成轻量级的synchronized volatile是一个变量修饰符,只能用来修饰变量。 volatile写:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。 volatile读:当读一个vo ......

synchronized是一个重量级的锁,volatile通常被比喻成轻量级的synchronized

volatile是一个变量修饰符,只能用来修饰变量。

volatile写:当写一个volatile变量时,jmm会把该线程对应的本地内存中的共享变量刷新到主内存。

volatile读:当读一个volatile变量时,jmm会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

volatile实现原理

1)jmm把内存屏障指令分为下列四类:

Java并发(一):Java内存模型干货总结

storeload barriers是一个“全能型”的屏障,它同时具有其他三个屏障的效果。现代的多处理器大都支持该屏障(其他类型的屏障不一定被所有处理器支持)。执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(buffer fully flush)。

store:数据对其他处理器可见(即:刷新到内存)

load:让缓存中的数据失效,重新从主内存加载数据 

2)jmm针对编译器制定的volatile重排序规则表

是否能重排序 第二个操作
第一个操作 普通读/写 volatile读 volatile写
普通读/写     no
volatile读 no no no
volatile写   no no

 

 

 

 

 

 

举例来说,第三行最后一个单元格的意思是:在程序顺序中,当第一个操作为普通变量的读或写时,如果第二个操作为volatile写,则编译器不能重排序这两个操作。

从上表我们可以看出:

  • 当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。
  • 当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。
  • 当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。

jmm内存屏障插入策略(编译器可以根据具体情况省略不必要的屏障):

  • 在每个volatile写操作的前面插入一个storestore屏障。
    • 对于这样的语句store1; storestore; store2,在store2及后续写入操作执行前,保证store1的写入操作对其它处理器可见。
  • 在每个volatile写操作的后面插入一个storeload屏障。
    • 对于这样的语句store1; storeload; load2,在load2及后续所有读取操作执行前,保证store1的写入对所有处理器可见。
  • 在每个volatile读操作的后面插入一个loadload屏障。
    • 对于这样的语句load1; loadload; load2,在load2及后续读取操作要读取的数据被访问前,保证load1要读取的数据被读取完毕。
  • 在每个volatile读操作的后面插入一个loadstore屏障。
    • 对于这样的语句load1; loadstore; store2,在store2及后续写入操作被刷出前,保证load1要读取的数据被读取完毕。

volatile保证可见性

  volatile修饰的变量写之后将本地内存刷新到主内存,保证了可见性

volatile保证有序性

  volatile变量读写前后插入内存屏障以避免重排序,保证了有序性

volatile不保证原子性

  volatile不是锁,与原子性无关

  要我说,由于cpu按照时间片来进行线程调度的,只要是包含多个步骤的操作的执行,天然就是无法保证原子性的。因为这种线程执行,又不像数据库一样可以回滚。如果一个线程要执行的步骤有5步,执行完3步就失去了cpu了,失去后就可能再也不会被调度,这怎么可能保证原子性呢。

为什么synchronized可以保证原子性 ,因为被synchronized修饰的代码片段,在进入之前加了锁,只要他没执行完,其他线程是无法获得锁执行这段代码片段的,就可以保证他内部的代码可以全部被执行。进而保证原子性。(摘自http://www.hollischuang.com/archives/2673)

volatile不保证原子性的例子:

/**
 * 创建10个线程,然后分别执行1000次i++操作。目的是程序输出结果10000
 * 但是,多次执行的结果都小于10000。这其实就是volatile无法满足原子性的原因。
 */
public class test {
    public volatile int inc = 0;

    public void increase() {
        inc++;
    }

    public static void main(string[] args) {
        final test test = new test();
        for (int i = 0; i < 10; i++) {
            new thread() {
                public void run() {
                    for (int j = 0; j < 1000; j++)
                        test.increase();
                };
            }.start();
        }

        while (thread.activecount() > 1)
            // 保证前面的线程都执行完
            thread.yield();
        system.out.println(test.inc);
    }
}

 

参考资料

深入理解java中的volatile关键字

java并发(一):java内存模型干货总结

【死磕java并发】—–深入分析volatile的实现原理