您的位置:首页 > 编程语言 > Java开发

JAVA并发读书笔记——volatile与synchronized

2017-11-26 22:42 225 查看

JAVA并发读书笔记——volatile与synchronized

在多线程并发编程中,volatile与synchronized都扮演着重要的角色,volatile可以说是轻量级synchronized,它在多处理器开发中保证了共享变量的“可见性”。如果volatile比使用synchronized的成本更低,因为它不会引起上下文的切换和调度。

volatile

JAVA语言规范第三版中volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能够被准确和一致的更新,线程应该确保通过排它锁单独获取这个变量。

首先我们要先意识到有这样的现象,编译器为了加快程序运行的速度,对一些变量的写操作会先在寄存器或者是CPU缓存上进行,最后才写入内存.而在这个过程中,变量的新值对其他线程是不可见的.

那么volatile是如何来保证可见性的呢?

java代码如下:

instance = new Singleton() //instance是volatile变量


转成汇编代码如下:

0x01a3deld:movb $0X0,0X1104800(%esi);0X01a3de24: lock addl $0X0,(%esp)


有volatile变量修饰的共享变量进行写操作的时候,会多出第二行汇编代码,有个明显的lock指令。Lock前缀的指令在多核处理器下会引发两件事情:

1)将当前处理器缓存行的数据写回到系统内存

2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效

这就是volatile的两条实现原则

volatile的使用优化

Java7中加入了JSR 166y规范对集合类和并发类库的改进。其中的一项是增加了接口TransferQueue和其实现类LinkedTransferQueue。

TransferQueue继承了BlockingQueue(BlockingQueue又继承了Queue)并扩展了一些新方法。BlockingQueue(和Queue)是Java 5中加入的接口,它是指这样的一个队列:当生产者向队列添加元素但队列已满时,生产者会被阻塞;当消费者从队列移除元素但队列为空时,消费者会被阻塞。

它在使用volatile变量时,用一种追加字节码的方式来优化队列的出队和入队。为什么将共享变量追加64字节能够提高并发编程的效率呢?因为对于英特尔i7、酷睿、Atom忽然NetBurst,以及Core Solo和Pentium M处理器L1、L2或L3缓存的高速缓存行是64个字节宽,不支持部分填充缓存行,这意味着,如果队列的头结点和尾节点都不足64字节的话,处理器会将他们都读到一个高速缓存行,在多处理器下每个处理器都会缓存同样的头尾节点,当一个处理器试图修改头结点时,会将整个缓存行锁定,那么在缓存一致性机制的作用下,会导致其他处理器不能访问自己高速缓存中的尾节点。而队列的入队和出队操作需要不停的修改头结点和尾节点。所以在多处理器的情况下,将会颜值影响到队列的入队和出队效率。使用追加到64字节的方式来填满告诉缓冲区的缓存行,避免头结点和尾节点加载到同一个缓存行,使头、尾节点在修改时,不会互相锁定。

那么是不是在使用volatile变量时都应该追加到64字节呢?

显然不是的,在下面两张场景下不应该使用这种方式

缓存行非64字节宽的处理器。如P6系列和奔腾处理器。它们是32字节宽

共享变量不会被频繁的写

synchronized

在多线程并发编程中synchronized一直都是元老级角色,很多人都会称呼塔为重量级锁。但是随着JAVA SE 1.6对synchronized进行了各种优化之后,有些情况下它就并不那么重了。SE 1.6中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程。

先来看看利用synchronized实现同步的基础:java中的每一个对象都可以作为锁具体表现为以下三种形式:

对于普通同步方法,锁是当前实例对象

对于静态同步方法,锁是当前类的class对象

对于同步方法块,锁是synchronized括号里配置的对象

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或者抛出异常时必须释放锁。那么这个锁到底存在哪里呢?锁里面会存储什么信息呢?

在JVM规范中可以看到,synchronized在JVM里的实现原理,JVM基于进入和退出monitor对象来实现方法同步和代码块同步,但两者的实现原理不一样。

volatile与synchronized

最后总结一下volatile与synchronized的区别与特点

1)volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.

2)volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.

3)volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性.

《Java编程思想》上说,定义long或double变量时,如果使用volatile关键字,就会获得(简单的赋值与返回操作)原子性

4)volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.

5)当一个域的值依赖于它之前的值时,volatile就无法工作了,如n=n+1,n++等。如果某个域的值受到其他域的值的限制,那么volatile也无法工作,如Range类的lower和upper边界,必须遵循lower<=upper的限制。

6)使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域

值得强调的是:关键字volatile解决的是变量在多个线程之间的可见性;而关键字synchronized解决的是多线程之间访问资源的同步性。volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它将私有内存和公共内存中的数据做同
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: