时间:2021-05-20
这篇文章主要介绍了Java内存模型原子性原理及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
本文就具体来讲讲JMM是如何保证共享变量访问的原子性的。
原子性问题
原子性是指:一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。
下面就是一段会出现原子性问题的代码:
public class AtomicProblem { private static Logger logger = LoggerFactory.getLogger(AtomicProblem.class); public static final int THREAD_COUNT = 10; public static void main(String[] args) throws Exception { BankAccount sharedAccount = new BankAccount("account-csx",0.00); ArrayList<Thread> threads = new ArrayList<>(); for (int i = 0; i < THREAD_COUNT; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 1000 ; j++) { sharedAccount.deposit(10.00); } } }); thread.start(); threads.add(thread); } for (Thread thread : threads) { thread.join(); } logger.info("the balance is:{}",sharedAccount.getBalance()); } public static class BankAccount { private String accountName; public double getBalance() { return balance; } private double balance; public BankAccount(String accountName, double balance){ this.accountName = accountName; this.balance =balance; } public double deposit(double amount){ balance = balance + amount; return balance; } public double withdraw(double amount){ balance = balance - amount; return balance; } public String getAccountName() { return accountName; } public void setAccountName(String accountName) { this.accountName = accountName; } }}上面的代码中开启了10个线程,每个线程会对共享的银行账户进行1000次存款操作,每次存款10块,所以理论上最后银行账户中的钱应该是10 * 1000 * 10 = 100000块。我执行了多次上面的代码,很多次最后的结果的确是100000,但是也有几次的结果并不是我们预期的。
14:40:25.981 [main] INFO com.csx.demo.spring.boot.concurrent.jmm.AtomicProblem - the balance is:98260.0出现上面结果的原因就是因为下面的操作并不是原子操作,其中的balance是一个共享变量。在多线程环境下可能会被打断。
balance = balance + amount;上面的赋值操作被分为多步执行完成,下面简单解析下两个线程对balance同时加10的过程(模拟存款过程,假设balance的初始值还是0)
线程1从共享内存中加载balance的初始值0到工作内存线程1对工作内存中的值加10//此时线程1的CPU时间耗尽,线程2获得执行机会线程2从共享内存中加载balance的初始值到工作内存,此时balance的值还是0线程2对工作内存中的值加10,此时线程2工作内存中的副本值是10线程2将balance的副本值刷新回共享内存,此时共享内存中balance的值是10//线程2CPU时间片耗尽,线程1又获得执行机会线程1将工作内存中的副本值刷新回共享内存,但是此时副本的值还是10,所以最后共享内存中的值也是10上面简单模拟了一个原子性问题导致程序最终结果出错的过程。
JMM对原子性问题的保证
自带原子性保证
在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作。
a = true; //原子性a = 5; //原子性a = b; //非原子性,分两步完成,第一步加载b的值,第二步将b赋值给aa = b + 2; //非原子性,分三步完成a ++; //非原子性,分三步完成synchronized
synchronized可以保证操作结果的原子性。synchronized保证原子性的原理也很简单,因为synchronized可以防止多个线程并发执行一段代码。还是用上面存款的场景做列子,我们只需要将存款的方法设置成synchronized的就能保证原子性了。
public synchronized double deposit(double amount){ balance = balance + amount; //1 return balance; }加了synchronized后,当一个线程没执行完deposit这个方法前,其他线程是不能执行这段代码的。其实我们发现synchronized并不能将上面的代码1编程原子性操作,上面的代码1还是有可能被中断的,但是即使被中断了其他线程也不能访问共享变量balance,当之前被中断的线程继续执行时得到的结果还是正确的。
因此synchronized对原子性问题的保证是从最终结果上来保证的,也就是说它只保证最终的结果正确,中间操作的是否被打断没法保证。这个和CAS操作需要对比着看。
Lock锁
Lock锁保证原子性的原理和synchronized类似,这边不进行赘述了。
原子操作类型
public static class BankAccount { //省略其他代码 private AtomicDouble balance; public double deposit(double amount) { return balance.addAndGet(amount); } //省略其他代码}JDK提供了很多原子操作类来保证操作的原子性。原子操作类的底层是使用CAS机制的,这个机制对原子性的保证和synchronized有本质的区别。CAS机制保证了整个赋值操作是原子的不能被打断的,而synchronized值能保证代码最后执行结果的正确性,也就是说synchronized能消除原子性问题对代码最后执行结果的影响。
简单总结
在多线程编程环境下(无论是多核CPU还是单核CPU),对共享变量的访问存在原子性问题。这个问题可能会导致程序错误的执行结果。JMM主要提供了如下的方式来保证操作的原子,保证程序不受原子性问题的影响。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
这篇文章主要介绍了Java原子变量类原理及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下一、原子变量
背景:听说VolatileJava高阶语法亦是挺进BAT的必经之路。Volatile:volatile同步机制又涉及Java内存模型中的可见性、原子性和有序性,
前面一篇文章在介绍Java内存模型的三大特性(原子性、可见性、有序性)时,在可见性和有序性中都提到了volatile关键字,那这篇文章就来介绍volatile关
内存模型中的同步模式(memorymodelsynchronizationmodes)原子变量同步是内存模型中最让人感到困惑的地方.原子(atomic)变量的主
浅谈java内存模型不同的平台,内存模型是不一样的,但是jvm的内存模型规范是统一的。其实java的多线程并发问题最终都会反映在java的内存模型上,所谓线程安