深刻理解java虚拟机(6)---内存模型与线程 & Volatile

其实关于线程的使用,以前已经写过博客讲解过这部分的内容:html

http://www.cnblogs.com/deman/category/621531.htmljava

JVM里面关于多线程的部分,主要是多线程是如何实现的,以及高效并发。android

1.Java内存模型

CPU在运行的时候,不可能把全部的东西都放在寄存器里面,全部须要使用内存。这个内存就是咱们知道的那个内存。编程

可是实际状况是,内存的读写速度于CPU的指令操做差了几个数量级。因此为了跟高效的使用CPU,就有高速缓存这么一个东西。缓存

如下是Intel 酷睿i7 6700K参数:安全

三级缓存8MB多线程

百度如下就知道这个“三级缓存”是个神马东西。并发

而java的内存模型与物理结构很是相识,有一个主内存,对应咱们计算机的内存,还有每一个线程都有一个工做内存,对应于高速缓存。jvm

 

能够看到,每一个java线程都有本身独立的内存。ide

这也就解释了,为何不一样线程,若是不一样步的话,变量就会有并发的问题。

这里关于工做内存和主内存的拷贝问题,是由JVM实现的,并非正真意义上的内存复制。

2.内存间操做

1)lock,做用于主内存变量,把一个变量标记为线程独占。

2)unlock,与lock正相反。

3)read,做用于主内存变量,它把一个变量从主内存传输到工做内存中。

4)load,做用于工做内存变量,把从read里面获取的变量放入工做内存的变量副本中。

5)use,做用于工做内存变量,把变量的值传递给执行引擎。

6)assign,做用于工做内存变量,把执行引擎的值 复制给工做内存变量。同use相反

7)store,做用于工做内存变量,把工做内存变量传输到主内存中。

8)write,做用于主内存变量,把store获取的值,写入到住内存中的变量。

read & load, store & write成对出现。

还有其余一些规则,目的就是保证内存变量的 操做合理,有序。

3.并发编程的三个概念

1)原子性

计一个操做要么所有执行,要么不执行,不能被打断。

jvm经过lock & unlock指令来保证代码的原子性。反映到java代码就是synchronized.

2)可见性

可见性是指当一个程序修改变量之后,其余程序能够当即得到这个修改的值。

3)有序性

JVM在编译java代码,优化的时候,会重现排布java代码的顺序。可是会保证结果时候java代码的顺序结果一致的。

public Runnable mRun1 = new Runnable() {
@Override
public void run() {
int a = readFileName();
writeFile(a);
initialized = true;
}
};

上面readFileName 和initialized = true;没有必然关系,因此在实际执行的时候,可能会先执行initialized = true;

对于这个线程内的结果没有影响。

可是若是是多线程的状况下:

    public Runnable mRun2 = new Runnable() {
        @Override
        public void run() {
            while (!initialized)
            {
                try {
                    TraceLog.i("sleep");
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            doSomeThing(context);
        }
    };

initialized = true的执行顺序对线程2的结果有直接的影响。全部有序性在这种状况下,须要保证。

通常java里面用synchronized就能够保证。可是过多的synchronized会对性能有很大的损失。

4.volatile关键字

volatile关键字修饰后的变量.有2个做用:

1)用来确保将变量的更新操做通知到其余线程,保证了新值能当即同步到主内存,以及每次使用前当即从主内存刷新.

当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的.

可是volatile 不能保证线程是安全的,由于java里面的运算并不是原子操做。2)volatile还有一个特性就是保证指令不从新排序。如今编译器,为了优化代码,都会从新排序指令。若是在多个线程里面,就会有很大的问题。

可是指令重排是JVM在它认为合理的状况下作的,因此很难模拟出这一状况。

    boolean aBoolean = false;
    public Runnable mRun1 = new Runnable() {
        @Override
        public void run() {
            aBoolean = false;
            while (!aBoolean)
            {
                doSomeThing();
            }
        }
    };

    public Runnable mRun2 = new Runnable() {
        @Override
        public void run() {
            aBoolean = true;
        }
    };

只是2个线程的例子,线程2用来关闭线程1.通常状况下,它会运行良好,可是有小几率状况下,会有问题。

aBoolean 在赋值为true的时候,没有马上被同步到主内存,而这时候线程1的工做内存aBoolean 的拷贝是false。

因此会陷入死循环。

volatile关键字就能够避免这种状况的发生。

1)当aBoolean = true;发生后,线程2会当即把aBoolean 的值更新到主内存。

2)线程1在使用到aBoolean 是,会首先到主内存从新获取新的值。而后更新工做内存中的值,这个时候 aBoolean就是true了,循环退出。

5.volatile 保证原子操做吗?

volatile不能保证线程是安全的。

package com.joyfulmath.jvmexample.multithread;

import com.joyfulmath.jvmexample.TraceLog;

import java.util.concurrent.CountDownLatch;

/**
 * @author deman.lu
 * @version on 2016-05-26 14:34
 */
public class VolatileTest2 {
    public volatile int inc = 0;
    static CountDownLatch countDownLatch = new CountDownLatch(10);
    public void increase() {
        inc++;
    }

    public static void main() {
        TraceLog.i();
        final VolatileTest2 test = new VolatileTest2();
        for(int i=0;i<200;i++){
            new Thread(){
                @Override
                public void run() {
                    super.run();
                    for(int j=0;j<50;j++)
                        test.increase();

                    countDownLatch.countDown();
//                    TraceLog.i(String.valueOf(Thread.currentThread().getId()));
                }
            }.start();
        }

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(test.inc);
        TraceLog.i(String.valueOf(test.inc));
    }
}
05-26 14:48:06.060 15209-15209/com.joyfulmath.jvmexample I/System.out: 9950
05-26 14:48:06.061 15209-15209/com.joyfulmath.jvmexample I/VolatileTest2: main: 9950 [at (VolatileTest2.java:41)]

结果并非10000,缘由就是 自增函数不是原子操做,而Volatile只能保证数值是更新到住内存,可是,当线程1执行过程当中假设inc=5,线程2可能已经获取了inc的值。

这个时候,线程1,++之后变为6,线程2也是6,并且由于主内存的值 & 线程2的值一致,就不会触发其余线程无效的状况,因此线程3取到的值,仍是6.全部这个数值的结果是没法确认的,可是<10000.

But, 我在android23下编译,发现一直是10000.不清楚缘由???

6.volatile的有序性

volatile只能保证部分有序性,好比说:

1 volatile boolean initialized = false;
2         public void run() {
3             context = readFileName();
4             writeFile(context);
5             initialized = true;
6             play();
7             Drawable();
8         }

上面,3,4两行语句顺序是乱序的,6,7也是,可是5 必定在3,4以后运行。 也就是5的执行为止不变,并且,3,4 不能和6,7互换执行顺序。这就是volatile有限的有序性。

 

参考:

http://www.cnblogs.com/dolphin0520/p/3920373.html

《深刻理解java虚拟机》周志明

相关文章
相关标签/搜索