栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

005 volatile不能保证原子性之i++操作

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

005 volatile不能保证原子性之i++操作

1、多线程对volatile修饰的变量做 count++ 操作,没有保证原子性

下面启动了1000个线程对volatile修饰的变量进行i++操作,我们可能会想每一个线程都+1,当1000个线程都执行完+1操作之后结果应该是1000,但是执行代码试试,结果可能不是1000。这说明volatile不是原子性的。

public class Counter {
	public static volatile int count = 0;

	public static void inc() {
		try {
			// sleep 1 毫秒让效果明显
			Thread.currentThread().sleep(1);
		} catch (Exception e) {
			e.printStackTrace();
		}
		count++;
	}

	public static void main(String[] args) {

		// 同时启动1000个线程,去进行i++计算,看看实际结果
		for (int i = 0; i < 1000; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					Counter.inc();
				}
			}).start();
		}

		// 保证前面的线程都执行完
		while (Thread.activeCount() > 1) {
			Thread.yield();
		}

		// 这里每次运行的值都有可能不同,可能不是1000
		System.out.println("运行结果:Counter.count=" + Counter.count);
	}
}
2、多线程对volatile修饰的变量做 count++ 操作,没有保证原子性。解决方案一

去掉volatile关键字,在 count++ 的方法上加 synchronized 关键字;或者不去掉volatile关键字,在 count++ 的方法上加 synchronized 关键字

public class Counter {
	public static int count = 0;

	public synchronized static void inc() {
		try {
			// sleep 1 毫秒让效果明显
			Thread.currentThread().sleep(1);
		} catch (Exception e) {
			e.printStackTrace();
		}
		count++;
	}

	public static void main(String[] args) {

		// 同时启动1000个线程,去进行i++计算,看看实际结果
		for (int i = 0; i < 1000; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					Counter.inc();
				}
			}).start();
		}

		// 保证前面的线程都执行完
		while (Thread.activeCount() > 1) {
			Thread.yield();
		}

		// 这里每次运行的值都相同,一定是1000
		System.out.println("运行结果:Counter.count=" + Counter.count);
	}
}

synchronized 是重量级锁,开销很大。

3、多线程对volatile修饰的变量做 count++ 操作,没有保证原子性。解决方案二

使用AtomicInteger里面的getAndIncrement()方法

import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
	public static AtomicInteger count = new AtomicInteger(0);

	public static void inc() {
		try {
			// sleep 1 毫秒让效果明显
			Thread.currentThread().sleep(1);
		} catch (Exception e) {
			e.printStackTrace();
		}
		count.getAndIncrement();
	}

	public static void main(String[] args) {

		// 同时启动1000个线程,去进行i++计算,看看实际结果
		for (int i = 0; i < 1000; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					Counter.inc();
				}
			}).start();
		}

		// 保证前面的线程都执行完
		while (Thread.activeCount() > 1) {
			Thread.yield();
		}

		// 这里每次运行的值都相同,一定是1000
		System.out.println("运行结果:Counter.count=" + Counter.count);
	}
}
4、多线程对volatile修饰的变量做 count++ 操作,没有保证原子性。假如你使用的是JDK8 ,可以考虑解决方案三,LongAdder。

LongAdder,(高竞争环境下(比如1000个线程竞争的情况),LongAdder 比 AtomicLong 的性能高)

5、为什么volatile修饰的变量做count++操作不具有原子性

例如你让一个volatile的integer自增(i++),其实要分成3步:
1)读取volatile变量值到local(线程本地内存);
2)(在线程的本地内存)增加变量的值;
3)把local的值写回(写回到主内存),让其它的线程可见。这3步的jvm指令为:

mov    0xc(%r10),%r8d ; Load
inc    %r8d           ; Increment
mov    %r8d,0xc(%r10) ; Store
lock addl $0x0,(%rsp) ; StoreLoad Barrier

最后一步是内存屏障。从Load到store到内存屏障,一共4步,其中最后一步jvm让这个最新的变量的值在所有线程可见,也就是最后一步让所有的CPU内核都获得了最新的值,但中间的几步(从Load到Store)是不安全的,中间如果其他的CPU修改了值将会丢失(???)。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/759686.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号