记得刚开始学习Java的时候,遇到多线程,就是synchronized。与当时的我们相比,synchronized是多么的神奇和强大。那个时候,我们给它起了个名字“同步”,也成了我们解决多线程情况的一剂良药。但是,随着学习的深入,我们知道synchronized是一个重量级的锁。和Lock相比,它会显得很笨重,以至于我们认为它没有那么高效而慢慢抛弃它。诚然随着JavsSE1.6对synchronized的各种优化,synchronized不会显得那么沉重。下面就跟随LZ一起探讨一下synchronized的实现机制,Java是如何对其进行优化的,锁优化机制,锁的存储结构以及升级过程;synchronized的实现原理可以保证一个方法或者代码块在运行的时候一次只有一个方法可以进入临界区,也可以保证共享变量的内存可见性。Java中的每个对象都可以用作锁。这是synchronized同步的基础:普通的同步方法,锁是当前实例对象的静态同步方法,锁是当前类的类对象同步方法块,锁是括号里的对象。当线程访问同步代码块时,首先需要获取锁才能执行同步代码。当退出或抛出异常时,必须释放锁,那么这个机制是如何实现的呢?先看一段简单的代码:1234567891011publicclassSynchronizedTest{publicsynchronizedvoidtest1(){}publicvoidtest2(){synchronized(this){}}}使用javap工具查看生成的Class文件信息分析Synchronize的实现从上面我们可以看出同步代码块是使用monitorenter和monitorexit指令实现的,同步的方法(这里看不到,需要查看JVM的底层实现)依赖方法修饰符accomplish上的ACC_SYNCHRONIZED。同步代码块:在同步代码块的开头插入monitorenter指令,在同步代码块的末尾插入monitorexit指令。JVM需要保证每个monitorenter都有对应的monitorexit。任何对象都有一个与之关联的监视器,当一个监视器被持有时,它就会被锁定。当线程执行monitorenter指令时,会尝试获取对象对应的monitor的所有权,即尝试获取对象的锁;同步方法:synchronized方法会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面,没有专门的指令来实现synchronized修饰的方法。而是在Class文件的方法表中将方法的access_flags字段中的synchronized标志设置为1,表示该方法为同步方法,用于调用该方法的对象或该方法所属的Class将Klass表示为JVM内部对象中的一个锁对象。(摘自:http://www.cnblogs.com/javaminer/p/3889023.html)下面继续分析,但是在深入之前,我们需要了解两个重要的概念:Java对象头,Monitor。Java对象头、monitorJava对象头和monitor是实现synchronized的基础!下面将详细描述这两个概念。synchronizedJava对象头使用的锁存在于Java对象头中,那么什么是Java对象头呢?Hotspot虚拟机的对象头主要包括两部分数据:MarkWord(标记字段)、KlassPointer(类型指针)。其中,KlassPoint是对象指向其类元数据的指针。虚拟机通过这个指针来判断这个对象是哪个类的实例。MarkWord用于存储对象本身的运行时数据。它实现了轻量级锁和偏向锁的关键,所以下面将重点介绍MarkWord。MarkWord用于存储对象本身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。Java对象头一般占用两个机器码(在32位虚拟机中,一个机器码等于4个字节,也就是32bit),但是如果对象是数组类型,则需要三个机器码,因为JVM虚拟机可以通过Java对象的元数据信息来确定Java对象的大小,但是从数组的元数据中无法确定数组的大小,所以用一个block来记录数组的长度。下图是Java对象头的存储结构(32位虚拟机):对象头信息是额外的存储成本,与对象本身定义的数据无关,但考虑到对象的空间效率虚拟机,MarkWord被设计成一个非固定的数据结构,就是在一个非常小的空间内存中存储尽可能多的数据,它会根据对象的状态重用自己的存储空间,也就是说,MarkWord会随着程序的运行而变化,变化状态如下(32位虚拟机):Java对象头的简单介绍,接下来我们看Monitor。监控器什么是监控器?我们可以将其理解为一种同步工具,也可以将其描述为一种同步机制,通常被描述为一个对象。就像万物皆对象一样,所有的Java对象都是天生的管程,每一个Java对象都有成为管程的潜质,因为在Java的设计中,每一个Java对象从娘胎里出来都有一把无形的锁,叫做内部锁或监视器锁。Monitor是一个线程私有的数据结构。每个线程都有一个可用的监控记录列表,还有一个全局的可用列表。每个被锁定的对象都会关联一个monitor(对象头MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段,用于存放线程的IDownsthelock,表示锁已被该线程占用。其结构如下:Owner:NULL开头表示当前没有线程拥有该监控记录。当线程成功拥有锁时,保存线程ID,释放锁时置为NULL;EntryQ:与一个系统交互排他锁(信号量)相关联,阻塞所有试图锁定监听记录而失败的线程。RcThis:表示阻塞或等待监听记录的所有线程数。Nest:用于实现可重入锁的计数。HashCode:保存从对象头复制的HashCode值(也可能包括GC年龄)。Candidate:用于避免不必要的阻塞或等待线程唤醒,因为一次只有一个线程能成功拥有锁,如果前一个释放锁的线程每次都唤醒所有阻塞或等待的线程,会造成不必要的上下文切换(从阻塞到就绪然后由于竞争锁失败再次阻塞),导致严重的性能下降。Candidate只有两个可能的值。0表示没有线程需要被唤醒。1表示需要唤醒一个后继线程来竞争锁。摘自:Java中synchronized的实现原理及应用)我们知道synchronized是一个重量级的锁,效率不是很好。看起来没有那么重,那么JVM做了哪些优化呢?锁优化jdk1.6对锁的实现引入了大量优化,如自旋锁、自适应自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术,减少锁操作的开销。锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。他们将在激烈的竞争中逐步升级。请注意,锁可以升级但不能降级。这个策略是为了提高获取和释放锁的效率。自旋锁线程的阻塞和唤醒需要CPU从用户态切换到核心态。频繁的阻塞和唤醒对于CPU来说是一个沉重的工作负载,必然会给系统的并发性能带来很大的压力。同时我们发现,在很多应用中,对象锁的锁状态只会持续很短的时间,为了这短时间频繁的阻塞和唤醒线程是不值得的。所以引入自旋锁。什么是自旋锁?所谓自旋锁就是让线程先等待一段时间,而不是立即挂起,看持有锁的线程会不会很快释放锁。为什么要等?只是执行一个无意义的循环(自旋)。自旋等待不能代替阻塞,更不用说对处理器数量的要求了(多核,现在好像没有单核处理器了),虽然可以避免线程切换带来的开销,但是占用处理器时间.如果持有锁的线程快速释放锁,自旋的效率是非常好的。相反,自旋线程会白白消耗处理资源,不会做任何有意义的工作。如果不去厕所拉屎,会导致性能的浪费。因此,旋转等待时间(旋转次数)必须有一个限制。如果自旋超过了定义的时间,仍然没有获取到锁,就应该挂起。自旋锁在JDK1.4.2引入,默认关闭,但可以通过-XX:+UseSpinning开启,JDK1.6默认开启。同时默认旋转次数为10次,可以通过参数-XX:PreBlockSpin进行调整;如果通过参数-XX:preBlockSpin来调整自旋锁的自旋次数,会带来很多不便。如果我把参数调成10,但是系统中有很多线程在你刚退出的时候就释放了锁(多自旋一两次就可以获得锁),你是不是很尴尬?于是JDK1.6引入了自适应自旋锁来让虚拟机越来越智能。自适应自旋锁JDK1.6引入了一种更智能的自旋锁,即自适应自旋锁。所谓自适应,就是自旋的次数不再是固定的,它是由同一把锁上一次自旋的时间和锁拥有者的状态决定的。它是怎么做到的?如果线程自旋成功,那么下次自旋的次数会更多,因为虚拟机认为自从上次成功之后,很有可能这次自旋会再次成功,所以会让自旋一直等待.更多次。反之,如果对于某个锁,只有很少的自旋能够成功,那么以后需要锁的时候就会减少甚至省略自旋的次数,以免浪费处理器资源。有了自适应自旋锁,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁状态的预测会越来越准确,虚拟机也会越来越智能。锁消除为了保证数据的完整性,我们需要对这部分操作进行同步控制,但是在某些情况下,JVM检测到没有共享数据竞争的可能,这就是为什么JVM会消除这些同步锁。锁消除的基础是逃逸分析的数据支撑。如果没有竞争,为什么需要锁定呢?所以锁消除可以节省请求锁的无意义时间。变量转义是否需要对虚拟机进行数据流分析来判断,但是我们程序员不是很清楚吗?我们是否将同步放在我们知道没有数据竞争的代码块之前?但是有时候程序并不是我们想的那样?虽然我们没有展示锁的使用,但是当我们使用一些JDK内置的API时,比如StringBuffer、Vector、HashTable等,这时候就会有不可见的加锁操作。比如StringBuffer的append()方法和Vector的add()方法:12345678publicvoidvectorTest(){Vector
