java线程volatile_Java多线程之volatile

Java多线程是一个庞大的知识体系,这里对其中的volatile进行一个总结,理清他的来龙去脉。

CPU缓存

要搞懂volatile,首先得了解CPU在运行过程中的存储是如何处理的,其结构如图

R4BLSW7SV7J41CN8C3GH7TA1.jpg

CPU会把一些经常使用的数据缓存在cache中,避免每次都去访问较慢的memory。在单线程环境下,如果一个变量的修改都在cache中,自然不会有什么问题,可是在多线程环境中就可能是下面这个图的示意图(单核另当别论)

SL55HCTLZL7CS92NWC9N4O2S.jpg

CPU1 修改了一个变量a存入cache1,但是CPU2 在cache2中看到的a任然是之前的a,所以造成CPU1修改失效,我们来看看示例代码:

import java.util.concurrent.TimeUnit;

public class Counter {

private static boolean stop ;

//private static volatile boolean stop ;

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

Thread t = new Thread(new Runnable() {

@Override

public void run() {

int i = 0;

while (!stop) {

i++;

}

}

} );

t.start();

TimeUnit.MILLISECONDS .sleep(5);

stop = true;

}

}

在我的4核笔记本上运行结果:

7KI47Z3TLELVALO9OAS7GV3F.jpg

就一直运行着,没有停止(需要手工停止),这说明在主线程中修改的stop变量后,线程t没有读取到最新的stop的值,还一直是false。

volatile原理

volatile的原理就是,如果CPU1修改了一个变量a,不仅要修改自身的cache,还要同步到memory中去,并且使CPU2的cache中的变量a失效,如果CPU2要读取a,那么就必须到memory中去读取,这样就保证了不同的线程之间对于a的可见性,亦即,无论哪个线程,随时都能获得变量a最新的最新值。

我们来看看示例代码:

import java.util.concurrent.TimeUnit;

public class Counter {

//private static boolean stop ;

private static volatile boolean stop ;

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

Thread t = new Thread(new Runnable() {

@Override

public void run() {

int i = 0;

while (!stop) {

i++;

}

}

} );

t.start();

TimeUnit.MILLISECONDS .sleep(5);

stop = true;

}

}

在我的4核笔记本上运行结果:

77RKK6FKYYDYS8UKBJ616JE1.jpg

很快程序就结束了,说明线程t读到了经主线程修改后的stop变量,然后就停止了。

(例子源于《effective Java》)

volatile使用场景

状态标志

就像上面的代码里,把简单地volatile变量作为状态标志,来达成线程之间通讯的目的,省去了用synchronized还要wait,notify或者interrupt的编码麻烦。

替换重量级锁

在Java中synchronized 又称为重量级锁,能够保重JMM的几大特性:一致性,原子性,可见性。但是由于使用了锁操作,在一定程度上会有更高的性能消耗(锁的线程互斥性亦即资源消耗)。而volatile能提供可见性,原子性(单个变量操作,不是a++这种符合操作),所以在读写上,可以用volatile来替换synchronized的读操作,而写操作仍然有synchronized实现,能取得更好的性能。

import java.util.ArrayList;

import java.util.List;

public class Counter1 {

private class Count11 {

private int value;

public synchronized int getValue() {

return value;

}

public synchronized int increment() {

return value++;

}

}

// private class Count11 {

// private volatile int value=0;

// int getValue() { return value; }

// synchronized int increment() { return value++; }

// }

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

Counter1.Count11 count11 = new Counter1().new Count11();

List threadArrayList = new ArrayList<>();

final int[] a = {0};

Long allTime = 0l;

long startTime = System.currentTimeMillis();

for (int i = 0; i < 4; i++) {

Thread t = new Thread(() -> {

int b = 0;

for (int j = 0; j < 10000; j++) {

count11.increment();

a[0] = count11.getValue();

}

for (int j = 0; j < 10000; j++) {

b++;

a[0] = count11.getValue();

}

});

t.start();

threadArrayList.add(t);

}

for (Thread t : threadArrayList) {

try {

t.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

long endTime = System.currentTimeMillis();

allTime = ((endTime - startTime));

System.out.println("result: " + a[0] + ", average time: " + (allTime) + "ms");

}

}

volatile优化结果:

result: 40000, average time: 124ms

result: 40000, average time: 133ms

result: 40000, average time: 141ms

result: 40000, average time: 112ms

result: 40000, average time: 123ms

result: 40000, average time: 143ms

result: 40000, average time: 120ms

result: 40000, average time: 120ms

未优化结果:

result: 40000, average time: 144ms

result: 40000, average time: 150ms

result: 40000, average time: 149ms

result: 40000, average time: 165ms

result: 40000, average time: 134ms

result: 40000, average time: 132ms

result: 40000, average time: 157ms

result: 40000, average time: 138ms

result: 40000, average time: 158ms

可见使用volatile过后效果的确优于只使用synchronized的性能,不过试验中发现有个阈值,如果读取修改次数较小,比如1000以内,只使用synchronized效果略好,存取次数变大以后 volatile的优势才慢慢体现出来(次数达到10000的话,差距就在60ms左右)。

待挖掘

还有很多用法,在将来的学习中,不断总结与挖掘。

联想

无论处于应用的哪一层,优化的思路都是可以相互借鉴的,比如我们做一个服务集群,如果每一个节点都要保存所有用户的session,就很难使得session同步,我们就可以借鉴volatile这种思路,在集群之上搞一个调度器,如果某一个节点修改了一个用户session,就报告给调度器,然后调度器通知其他所有节点修改该用户session。而一般情况下,数据的读写比都比较高,所以这样做就能到达一个很好的性能。

注意事项

引用类型的volatile只在引用本身发生变化时具有可见性,其引用的对象的元素发生变化时不具有可见性

欢迎访问我的个人主页 mageek(mageek.cn)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
volatile关键字在Java中用于多线程编程中的可见性和有序性。 可见性:当一个线程修改了一个volatile修饰的变量的值时,其他线程能够立即看到这个变化。这是因为volatile关键字会告诉编译器和CPU缓存,这个变量可能会被其他线程修改,因此需要从主内存中读取最新的值。而普通的变量则可能会在CPU缓存中被复制一份,导致多线程之间无法共享变量的最新值。 有序性:volatile关键字能够保证被volatile修饰的变量的读写操作按照一定的顺序执行。对一个volatile变量的写操作会先行发生于后续对该变量的读操作,即保证了写操作的结果对其他线程是可见的。 需要注意的是,volatile关键字只能保证可见性和有序性,并不能保证原子性。如果需要保证原子性,可以使用synchronized关键字或者使用java.util.concurrent包中提供的原子类。 使用volatile关键字时需要注意以下几点: 1. volatile关键字只能修饰变量,不能修饰方法或者代码块。 2. 对于单个volatile变量的读写操作是原子的,但是对于多个volatile变量的复合操作不具备原子性。 3. volatile关键字不能替代synchronized关键字,volatile关键字适用于对变量的读写操作,而synchronized关键字适用于对代码块的同步操作。 4. volatile关键字对变量的修改具有即时可见性,但是并不能保证原子性,如果需要保证原子性,需要使用其他手段(如synchronized关键字或者原子类)。 总的来说,volatile关键字是Java多线程编程中用于保证可见性和有序性的一种机制。在需要多线程共享变量的场景中,合理使用volatile关键字可以提高程序的性能和正确性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值