从内存模型角度说明volatile与synchronized在并发特性方面的区别

目录

1.并发特性

2.线程、主内存和工作内存

3.volatile与synchronized

3.1 synchronized

3.1.1可见性

3.1.2有序性

3.1.3原子性

3.2 volatile

3.2.1可见性

3.2.2有序性

3.2.3原子性


1.并发特性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

有序性是指程序执行的顺序按照代码的先后顺序执行。

原子性是指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。 

2.线程、主内存和工作内存

主内存(main memory)和工作内存(working memory),线程之间数据的交互不是直接传递,而是通过共享变量来实现的。对象的创建是在主内存中,而线程用到该对象时,是先拷贝一个该对象的副本放到线程的工作内存中,然后对该副本对象进行操作,完成后在刷新到主内存中。

而这个操作过程中的几个步骤不是原子性的,这样就有可能造成读脏数据等问题。根据Java语言规范的规定:

线程的工作内存和主存间的数据交换是松耦合的,什么时候需要刷新工作内存或者什么时候更新主内存的内容,可以由具体的虚拟机实现自行决定。

在线程用到该对象,拷贝一个该对象的副本放到线程的工作内存中时,如果处理器没有缓存命中,那么线程就不直接从主内存(系统内存)读取数据,而是将主内存的数据读到工作内存(内部缓存)后再进行操作;反之如果命中,则直接从主内存中读取数据并操作。而且操作完成之后,不知道何时会写回主内存。

也就是说,每次线程调用变量时是直接取自己的工作内存中的值还是先从主内存复制再取是没有保证的,任何一种情况都可能发生。同样的,线程改变变量的值之后,是否马上写回到主内存上也是不可保证的,也许马上写,也许过一段时间再写。

那么,在多线程的应用场景下就会出现问题了,多个线程同时访问同一个代码块,很有可能某个线程已经改变了某变量的值,当然现在的改变仅仅是局限于工作内存中的改变,此时JVM并不能保证将改变后的值立马写到主内存中去,也就意味着有可能其他线程不能立马得到改变后的值,依然在旧的变量上进行各种操作和运算,最终导致不可预料的结果。

3.volatile与synchronized

3.1 synchronized

3.1.1可见性

synchronized可以修饰方法,代码块,实际上都是获取了一个对象作为锁,在线程进入synchronized块之前,会把工作存内存置为无效,必须从主内存上读取最新的值。而在线程退出synchronized块时,同样会把工作内存中的值映射到主内存。这样一来就可以强制其按照上面的顺序运行,以保证线程在执行完代码块后,工作内存中的值和主内存中的值是一致的,保证了数据的可见性。

3.1.2有序性

当有其他线程试图访问synchronized的方法时,必须要获取锁对象,但是由于锁被当前线程所占用,只能等待该线程释放锁对象,也就保证了有序性。

3.1.3原子性

这一点显而易见。

3.2 volatile

Java对volatile的定义如下: 

Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保排它锁单独获得这个变量。

volatile关键字用来修饰变量,只保证了可见性和有序性,没有保证原子性。

3.2.1可见性

当线程访问被volatile修饰的共享变量时,读取会直接从主内存中进行,写入也是直接修改主内存中的数据,这样,由于缺少拷贝的过程,其他线程访问该共享变量时,肯定是修改后的数据,即保证了可见性。针对读、写具体来说:

(1)当对volatile变量进行读操作时,线程会直接从主内存中读取,并且JMM会把该线程对应的工作内存置为无效;

(2)当对volatile变量进行写操作时,JVM就会向处理器发送一条Lock前缀的指令,该指令在单核处理器下会引发事1):

1)将当前工作内存的数据写回到主内存。

但是在多核处理器下,Lock前缀的指令会在1)的基础上引发另一件事2),但是就算写回到主内存,如果其他线程工作内存的数据还是旧的,再次执行计算操作就会有问题。所以有2)。

2)这个写回主内存的操作会在其他线程工作内存的数据无效:在多处理器下,为保证各个处理器(线程)的缓存(工作内存)一致,就会实现缓存一致性协议,如果各个处理器(线程)通过嗅探在总线上传播的数据来检查自己缓存的数据已经过期,便使自己线程的工作内存数据无效,当处理器对这个数据进行修改时,重新从主内存把数据读取到自己的工作内存里。

如果我们把读写两个步骤综合来看的话,线程A读一个volatile变量后,其他线程在写这个volatile变量之前所有共享变量的值都将对A可见。

3.2.2有序性

指令重排序的优化是指机器级的优化操作,汇编代码改变执行顺序。从硬件架构上讲,指令重排序是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给个相应电路单元处理,并保证得到正确的执行结果,最终提高运行速度。

指令重排序的优化会导致某些语句提前执行,导致有序性问题。volatile禁止指令重排序从而保证有序性。

3.2.3原子性

在不符合以下两条规则的运算场景中,我们仍然要通过加锁来保证原子性。

1)运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值;

如下图所示,在多线程环境中,use和assign多次出现,但这些操作并不是原子性,也就是在read之后,如果主内存count变量发生改变之后,工作内存中的值已经加载,不会产生相应的变化,也就出现了非线程安全问题。

 

2)变量不需要与其他的状态变量共同参与不变约束(不懂,求解释

 

参考:

[1]https://www.cnblogs.com/marcotan/p/4256906.html

[2]深入理解Java虚拟机

[3]Java并发编程的艺术

[4]多线程编程核心技术

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值