Four points clear difference in Java synchronized and volatile of

Author: Hollis

Recall two keywords: synchronized and volatile

1, Java programming language in order to solve complicated by the existence of atoms, visibility and order issues, and offers a range of concurrent processing related keywords, such as synchronized, volatile, final, concurren bags.

2, synchronized by locking a manner such that it requires atomicity, ordering and visibility when the three characteristics which can be used as a solution appears to be "universal" in. Indeed, most of the concurrency control operations can be done using synchronized.

. 3, by inserting volatile memory barrier before and after the operation of the variable volatile manner, to ensure the visibility of the scene under concurrent and orderliness variables.

4, volatile keyword can not guarantee atomicity, and synchronized by monitorenter and monitorexit two instructions can be synchronized to ensure that modified code can only be one thread access at the same time, to ensure that does not occur in a multi-CPU time slice switching between threads, atomicity can be guaranteed.

Well, we know, synchronized and volatile two keywords are two key Java concurrent programming frequently used, and, by the previous review, we know can ensure that synchronized concurrent programming will not appear atomicity, visibility and ordering problem, and volatile can only guarantee visibility and orderly, then, both born synchronized, HE Sheng volatile?

Next, we come to discuss about why Java has been synchronized keyword, but also to provide the volatile keyword.


synchronized problems

We all know that is a kind of synchronized locking mechanism, since it is locked, it is natural have the following disadvantages:

1, there is performance loss

Although JDK 1.6 in synchronized done a lot of optimization, such as adaptive spin locks elimination, lock coarsening, lightweight locks and lock biased, but after all he is also a lock.

Above these types of optimization is to try to find ways to avoid Monitor were locked, but not all cases can be optimized Moreover, even after optimization, optimization process is a certain time-consuming.

Therefore, whether to use synchronous or synchronous block method, or the operation to be performed before the synchronization lock, unlock after the synchronization operation is required, this locking, unlocking process is to have a loss of performance.

About performance comparison between the two, due to the elimination of many virtual machines and optimize the implementation of the lock, so it is difficult to quantify the performance gap between the two, but we can determine a basic principle is: the performance of read operations volatile variables trumpet common variable is almost no difference, but the write operation due to the need to insert a memory barrier it will be slower, even so, volatile in most scenes is lower than the lock overhead.

2, resulting in blockage

On principle synchronize implementation, both synchronous method or synchronized block, or whether it is ACC_SYNCHRONIZED monitorenter, monitorexit are based Monitor implementation.

Based Monitor object when multiple threads access for a synchronization code, will first enter Entry Set, when there is a thread to acquire the lock object to be The Owner region, other threads will continue to wait for Entry Set. And when a thread calls the wait method releases the lock and enter the Wait Set wait.

v2-1a01b027a0ab4156387a02b0b3cb8a30_b.jpg

Therefore, the lock is essentially synchronize achieve a blocking locks, that is to queue multiple threads access the same shared objects.

The volatile is a lightweight synchronization mechanism provided by the Java virtual machine, he is based on the memory barrier to achieve. After all, he was not locked, so he will not have a problem blocking and performance losses caused by synchronized.

v2-9305968b55b767dfa62ec747a1b5ac3c_hd.png

volatile additional features

In addition to the earlier mentioned volatile us better than synchronized performance, volatile there is also a nice addition, it is prohibited command rearrangement.

Let's take an example, take a look if only synchronized without using any volatile issue will happen, we took the more familiar single-case model run.

We achieve a singleton by way of double checking locks, do not use the volatile keyword here:

 1   public class Singleton {  
 2      private static Singleton singleton;  
 3       private Singleton (){}  
 4       public static Singleton getSingleton() {  
 5       if (singleton == null) {  
 6           synchronized (Singleton.class) {  
 7               if (singleton == null) {  
 8                   singleton = new Singleton();  
 9               }  
 10           }  
 11       }  
 12       return singleton;  
 13       }  
 14   }

以上代码,我们通过使用synchronized对Singleton.class进行加锁,可以保证同一时间只有一个线程可以执行到同步代码块中的内容,也就是说singleton = new Singleton()这个操作只会执行一次,这就是实现了一个单例。

但是,当我们在代码中使用上述单例对象的时候有可能发生空指针异常。这是一个比较诡异的情况。

我们假设Thread1 和 Thread2两个线程同时请求Singleton.getSingleton方法的时候:

v2-c7bac7349e472a829c06415fa0e519ed_b.jpg


  • Step1 ,Thread1执行到第8行,开始进行对象的初始化。

  • Step2 ,Thread2执行到第5行,判断singleton == null。

  • Step3 ,Thread2经过判断发现singleton != null,所以执行第12行,返回singleton。

  • Step4 ,Thread2拿到singleton对象之后,开始执行后续的操作,比如调用singleton.call()。

以上过程,看上去并没有什么问题,但是,其实,在Step4,Thread2在调用singleton.call()的时候,是有可能抛出空指针异常的。

之所有会有NPE抛出,是因为在Step3,Thread2拿到的singleton对象并不是一个完整的对象。

什么叫做不完整对象,这个怎么理解呢?

我们这里来先来看一下,singleton = new Singleton();这行代码到底做了什么事情,大致过程如下:

  • 1、虚拟机遇到new指令,到常量池定位到这个类的符号引用。

  • 2、检查符号引用代表的类是否被加载、解析、初始化过。

  • 3、虚拟机为对象分配内存。

  • 4、虚拟机将分配到的内存空间都初始化为零值。

  • 5、虚拟机对对象进行必要的设置。

  • 6、执行方法,成员变量进行初始化。

  • 7、将对象的引用指向这个内存区域。

我们把这个过程简化一下,简化成3个步骤:

  • a、JVM为对象分配一块内存M

  • b、在内存M上为对象进行初始化

  • c、将内存M的地址复制给singleton变量

如下图:

v2-193e00fd4cb8d20a788880b0d90390bf_hd.png

因为将内存的地址赋值给singleton变量是最后一步,所以Thread1在这一步骤执行之前,Thread2在对singleton==null进行判断一直都是true的,那么他会一直阻塞,直到Thread1将这一步骤执行完。

但是,问题就出在以上过程并不是一个原子操作,并且编译器可能会进行重排序,如果以上步骤被重排成:

  • a、JVM为对象分配一块内存M

  • c、将内存的地址复制给singleton变量

  • b、在内存M上为对象进行初始化


如下图:

v2-153e8a03ffd032f54384e46ed82828de_b.jpg

这样的话,Thread1会先执行内存分配,在执行变量赋值,最后执行对象的初始化,那么,也就是说,在Thread1还没有为对象进行初始化的时候,Thread2进来判断singleton==null就可能提前得到一个false,则会返回一个不完整的sigleton对象,因为他还未完成初始化操作。

这种情况一旦发生,我们拿到了一个不完整的singleton对象,当尝试使用这个对象的时候就极有可能发生NPE异常。

那么,怎么解决这个问题呢?因为指令重排导致了这个问题,那就避免指令重排就行了。

所以,volatile就派上用场了,因为volatile可以避免指令重排。只要将代码改成以下代码,就可以解决这个问题:

 1   public class Singleton {  
 2      private volatile static Singleton singleton;  
 3       private Singleton (){}  
 4       public static Singleton getSingleton() {  
 5       if (singleton == null) {  
 6           synchronized (Singleton.class) {  
 7               if (singleton == null) {  
 8                   singleton = new Singleton();  
 9               }  
 10           }  
 11       }  
 12       return singleton;  
 13       }  
 14   }

对singleton使用volatile约束,保证他的初始化过程不会被指令重排。这样就可以保Thread2 要不然就是拿不到对象,要不然就是拿到一个完整的对象。


synchronized的有序性保证呢?

看到这里可能有朋友会问了,说到底上面问题是发生了指令重排,其实还是个有序性的问题,不是说synchronized是可以保证有序性的么,这里为什么就不行了呢?

首先,可以明确的一点是:synchronized是无法禁止指令重排和处理器优化的。那么他是如何保证的有序性呢?

这就要再把有序性的概念扩展一下了。Java程序中天然的有序性可以总结为一句话:如果在本线程内观察,所有操作都是天然有序的。如果在一个线程中观察另一个线程,所有操作都是无序的。

以上这句话也是《深入理解Java虚拟机》中的原句,但是怎么理解呢?周志明并没有详细的解释。这里我简单扩展一下,这其实和as-if-serial语义有关。

as-if-serial语义的意思指:不管怎么重排序,单线程程序的执行结果都不能被改变。编译器和处理器无论如何优化,都必须遵守as-if-serial语义。

这里不对as-if-serial语义详细展开了,简单说就是,as-if-serial语义保证了单线程中,不管指令怎么重排,最终的执行结果是不能被改变的。

那么,我们回到刚刚那个双重校验锁的例子,站在单线程的角度,也就是只看Thread1的话,因为编译器会遵守as-if-serial语义,所以这种优化不会有任何问题,对于这个线程的执行结果也不会有任何影响。

但是,Thread1内部的指令重排却对Thread2产生了影响。

那么,我们可以说,synchronized保证的有序性是多个线程之间的有序性,即被加锁的内容要按照顺序被多个线程执行。但是其内部的同步代码还是会发生重排序,只不过由于编译器和处理器都遵循as-if-serial语义,所以我们可以认为这些重排序在单线程内部可忽略。


总结

本文从两方面论述了volatile的重要性以及不可替代性:

一方面是因为synchronized是一种锁机制,存在阻塞问题和性能问题,而volatile并不是锁,所以不存在阻塞和性能问题。

另外一方面,因为volatile借助了内存屏障来帮助其解决可见性和有序性问题,而内存屏障的使用还为其带来了一个禁止指令重排的附件功能,所以在有些场景中是可以避免发生指令重排的问题的。

So, in the future needs to be done when concurrency control, if not related to the atomic problem, you can prioritize the use of the volatile keyword.


At last

Welcome to share with everyone, like the point of a praise yo remember the article, thanks for the support!


Guess you like

Origin blog.51cto.com/14442094/2433101