synchronized是Java程序员最常用的同步工具,但是很多人对其用法和实现原理知之甚少,所以很多人认为synchronized是一个重量级的锁,相对低表现。差,尽量少用。但不可否认的是,synchronized依然是并发的首选工具,甚至连volatile、CAS、ReentrantLock都无法撼动synchronized的地位。同步是求职面试中的一项基本技能。今天就跟随一灯深入剖析synchronized的底层原理。1、synchronized的作用Synchronized是Java提供的隐式锁,不需要开发者手动加锁和释放锁。保证多线程并发情况下数据的安全,实现同一时间只能有一个线程访问资源,其他线程只能阻塞等待。简单的说就是互斥同步。2.synchronized的用法我们先来看看synchronized的用法。使用位置锁定对象的示例代码InstancemethodInstanceobjectpublicsynchronizedvoidmethod(){...}静态方法类classpublicstaticsynchronizedvoidmethod(){...}InstanceobjectInstanceobjectpublicvoidmethod(){Object对象=新对象();synchronized(obj){...}}classobjectclassclassclasspublicvoidmethod(){synchronized(Demo.class){...}}this关键字实例对象Publicvoidmethod(){synchronized(this){...}}可以看到加锁的对象只有两种,实例对象和类。由于可以通过类名直接访问静态方法,所以与直接在类class上加锁是一样的。当锁定实例方法、实例对象或this关键字时,锁定范围是当前实例对象。实例对象上的锁和类class上的锁不是互斥的。3、synchronized加锁的原理当我们使用synchronized对方法和对象进行加锁时,Java底层是如何实现加锁的呢?在类对象上加锁时,即加锁在class类上,代码如下:(SynchronizedDemo.class){System.out.println("Helloworld!");}}}反编译看源码实现:可以看到底层通过monitorenter和monitorexit这两个关键字加锁和释放Lock,在执行同步代码前使用monitorenter加锁,执行完后使用monitorexit释放锁同步代码,并在抛出异常时使用monitorexit释放锁。写成伪代码,类似如下:System.out.println("世界,你好!");monitorexit释放锁;}catch(Exceptione){monitorexit释放锁;}}}在给实例方法加锁的时候,底层是怎么实现的呢?代码如下:/***@authorYidengArchitecture*@apiNote同步示例**/publicclassSynchronizedDemo{publicstaticsynchronizedvoidmethod(){System.out.println("Helloworld!");}}然后反编译看看底层实现:这次只用了一个ACC_SYNCHRONIZED关键字来实现隐式加锁和释放锁。其实不管是ACC_SYNCHRONIZED关键字,还是monitorenter和monitorexit,底层都是通过获取monitor锁来加锁和释放的。监视器锁由ObjectMonitor实现。虚拟机中的ObjectMonitor数据结构如下(C++实现):ObjectMonitor(){_header=NULL;_count=0;//WaitSet和EntryList的节点数之和_waiters=0,_recursions=0;//重入次数_object=NULL;_owner=NULL;//持有锁的线程_WaitSet=NULL;//等待状态的线程会被添加到_WaitSet_WaitSetLock=0;_Responsible=NULL;_succ=NULL;_cxq=NULL;//多个线程竞争锁会先存入这个单向链表FreeNext=NULL;_EntryList=NULL;//处于等待锁块状态的线程会被添加到列表中_SpinFreq=0;_自旋时钟=0;所有者线程=0;}图中展示了ObjectMonitor的基本工作机制:当多个线程同时访问一段同步代码时,会先进入_EntryList队列等待。当一个线程获得对象的Monitor锁后,进入临界区,将Monitor中的_owner变量设置为当前线程,Monitor中的计数器_count加1,即获得对象锁。如果持有Monitor的线程调用wait()方法,当前持有的Monitor锁将被释放,_owner变量恢复为null,_count减1,同时线程进入_WaitSet集合并等待被唤醒。_WaitSet集合中的线程会再次被放入_EntryList队列,重新竞争获取锁。如果当前线程执行完毕,则释放Monitor,重置变量值,以便其他线程进入获取锁。线程竞争锁的过程比上图复杂。除了用来保存竞争线程的双向链表_EntryList,ObjectMonitor中还有一个单向链表_cxq,由两个队列共同管理并发线程。
