java数组内存模型,Java内存模型与volatile

Java内存模型(JMM)

在介绍volatile之前,先介绍一下java内存模型(JMM)。如下图所示:

a07b979bc933b6f4bc50f3c92ed5735d.png

每个Java线程在运行的过程中,都有一个与之对应的工作内存,这个内存空间是线程私有的。

当多个线程同时修改同一个对象时,线程会首先从主内存里面取出对象到工作内存当中去。然后更改完工作内存之后,再更新到主内存当中去。因此,对于普通的变量来说,在多线程操作的过程中,如果内部不做线程安全的控制,就存在着线程安全的问题。

volatile简单介绍

volatile关键字保证的是一种可见性。就是当一个线程修改了一个volatile类型的变量,在另外一个线程操作之前,会立即通知到其数据已经更新。这里面简单的介绍一下相关的语义:

class VolatileExample {

int a = 0;

volatile boolean flag = false;

public void writer() {

a = 1; //1

flag = true; //2

}

public void reader() {

if (flag) { //3

int i = a; //4

……

}

}

}

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

假设线程A执行writer()方法之后,线程B执行reader()方法。根据happens before规则,这个过程建立的happens before 关系可以分为两类:

根据程序次序规则,1 happens before 2; 3 happens before 4。

根据volatile规则,2 happens before 3。

根据happens before 的传递性规则,1 happens before 4。

上述happens before 关系的图形化表现形式如下:

0a2902cc766573d375b8eb602e6ebc65.png

在上图中,每一个箭头链接的两个节点,代表了一个happens before 关系。黑色箭头表示程序顺序规则;橙色箭头表示volatile规则;蓝色箭头表示组合这些规则后提供的happens before保证。

这里A线程写一个volatile变量后,B线程读同一个volatile变量。A线程在写volatile变量之前所有可见的共享变量,在B线程读同一个volatile变量后,将立即变得对B线程可见。

一个有趣的例子

public class Novisibility {

private static boolean ready;// 在server模式下,由于JVM优化,造成ReaderThread永远无法看到ready=true, 所以while会一直循环

private static int number;

private static class ReaderThread extends Thread {

@Override

public void run() {

while (!ready)

;

System.out.println(number);

}

}

public static void main(String[] args) throws InterruptedException {

new ReaderThread().start();

Thread.sleep(1000);

number = 42;

ready = true;

Thread.sleep(10000);

}

}

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

通过运行以上代码可以看见,程序无法退出。这是由于在-server模式下,JVM对指令进行了优化,造成了子线程中的ready永远都不知道ready的值已经变化。

volatile数组变量的可见性

这里面以 CopyOnWriteArrayList 源代码为例,介绍一下其内部是如何使用volatile关键字的。

/** The array, accessed only via getArray/setArray. */

private transient volatile Object[] array;

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

对于CopyOnWriteArrayList 内部保存数据的数组,使用了volatile 关键字。

public E set(int index, E element) {

final ReentrantLock lock = this.lock;

lock.lock();

try {

Object[] elements = getArray();

E oldValue = get(elements, index);

if (oldValue != element) {

int len = elements.length;

Object[] newElements = Arrays.copyOf(elements, len);

newElements[index] = element;

setArray(newElements);

} else {

// Not quite a no-op; ensures volatile write semantics

setArray(elements);

}

return oldValue;

} finally {

lock.unlock();

}

}

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

上面是CopyOnWriteArrayList 设置元素的代码,可以看出,这个并不是简单的在index位置上设置元素。而是生成了一个新的数组元素,然后将当前的array设置成新的newElements 。

通过数组的整体替换,才可以保证对其他线程的可见性。这里如果仅仅修改了索引为index的元素值。那么其他线程是无法立马看到结果的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值