第十二章 Java内存模型与线程

2017-03-28  本文已影响54人  骊骅

2、硬件的效率与一致性

在多处理器系统中,每个处理器都有自己的高速缓存,而它们又共享同一主内存(MainMemory),如图所示。当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致,如果真的发生这种情况,那同步回到主内存时以谁的缓存数据为准呢?为了解决一致性的问题,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作

3、Java内存模型

3.1 主内存与工作内存

3.2 内存间交互操作

操作名 作用内存域 描述
lock(锁定) 主内存 把一个变量标识为一条线程独占的状态
unlock(解锁) 主内存 它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read(读取) 主内存 把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
load(载入) 工作内存 把read操作从主内存中得到的变量值放入工作内存的变量副本中
use(使用) 工作内存 把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作
assign(赋值) 工作内存 把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
store(存储) 工作内存 把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用
write(写入) 主内存 把store操作从工作内存中得到的变量的值放入主内存的变量中

3.3 对于volatile型变量的特殊规则

以下两个场景适合使用volatile
【1】运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
【2】变量不需要与其他的状态变量共同参与不变约束


【不适合例子1】

package study12;

/**
 * Created by haicheng.lhc on 06/04/2017.
 *
 * @author haicheng.lhc
 * @date 2017/04/06
 */
public class VolatileTest {
    public static volatile int race = 0;

    public static void increase() {
        race++;
    }

    private static final int THREADS_COUNT = 20;

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        increase();
                    }
                }
            });
            threads[i].setName("theead"+i);
            threads[i].start();
        }

        // 等待所有累加线程都结束
//        while (Thread.activeCount() > 1)
//            Thread.yield();
        Thread.sleep(10000);

        System.out.println(race);
    }
}

这段代码发起了20个线程,每个线程对race变量进行10000次自增操作,如果这段代码能够正确并发的话,最后输出的结果应该是200000。读者运行完这段代码之后,并不会获得期望的结果,而且会发现每次运行程序,输出的结果都不一样,都是一个小于200000的数字.

原因分析:从字节码层面上很容易就分析出并发失败的原因了:当getstatic指令把race的值取到操作栈顶时,volatile关键字保证了race的值在此时是正确的,但是在执行iconst_1、iadd这些指令的时候,其他线程可能已经把race的值加大了,而在操作栈顶的值就变成了过期的数据,所以putstatic指令执行后就可能把较小的race值同步回主内存之中。

public static void increase();
  Code:
   Stack=2, Locals=0, Args_size=0
   0:   getstatic       #2; //Field race:I
   3:   iconst_1
   4:   iadd
   5:   putstatic       #2; //Field race:I
   8:   return
  LineNumberTable: 
   line 14: 0
   line 15: 8


【适合例子】

 volatile boolean shutdownRequested;
    public void shutdown(){
        shutdownRequested = true;
    }

    public void doWork(){
        while(!shutdownRequested){
            //do stuff
        }
    }

3.4 对于long和double型变量的特殊规则

3.5 原子性、可见性与有序性

3.6 先行发生原则

【问题】如果Java内存模型中所有的有序性都仅仅靠volatile和synchronized来完成,那么有一些操作将会变得很烦琐,但是我们在编写Java并发代码的时候并没有感觉到这一点呢?
【答案】这是因为Java语言中有一个“先行发生”(happens-before)的原则

下面是Java内存模型下一些“天然的”先行发生关系,这些先行发生关系无须任何同步器协助就已经存在,可以在编码中直接使用

上一篇 下一篇

猜你喜欢

热点阅读