jdk 6 对锁进行了优化,让他看起来再也不那么笨重,synchronized有三种形式:偏向锁,轻量级锁,重量级锁.数组
介绍三种锁以前,引入几个接下来会出现的概念安全
mark work:
对象头,对象头中存储了一些对象的信息,这个是锁的根本,任何锁都须要依赖mark word 来维持锁的运做,对象头中存储了当前持有锁的线程,hashCode,GC的一些信息都存储在对象头中.
在JVM中,对象在内存中除了自己的数据外还会有个对象头,对于普通对象而言,其对象头中有两类信息:mark word和类型指针。另外对于数组而言还会有一份记录数组长度的数据.
类型指针是指向该对象所属类对象的指针,mark word用于存储对象的HashCode、GC分代年龄、锁状态等信息。在32位系统上mark word长度为32bit,64位系统上长度为64bit。为了能在有限的空间里存储下更多的数据,其存储格式是不固定的,在32位系统上各状态的格式以下:数据结构
能够看到锁信息也是存在于对象的mark word中的。当对象状态为偏向锁时,mark word存储的是偏向的线程ID;当状态为轻量级锁时,mark word存储的是指向线程栈中Lock Record的指针;当状态为重量级锁时,为指向堆中的monitor对象的指针.工具
Lock Record:
前面对象头中提到了Lock Record,接下来讲下Lock Record,Lock Record存在于线程栈中,翻译过来就是锁记录,它会拷贝一份对象头中的mark word信息到本身的线程栈中去,这个拷贝的mark word 称为Displaced Mark Word ,另外还有一个指针指向对象性能
monitor:
monitor存在于堆中,什么是Monitor?咱们能够把它理解为一个同步工具,也能够描述为一种同步机制,它一般被描述为一个对象。优化
与一切皆对象同样,全部的Java对象是天生的Monitor,每个Java对象都有成为Monitor的潜质,由于在Java的设计中 ,每个Java对象自打娘胎里出来就带了一把看不见的锁,它叫作内部锁或者Monitor锁。spa
Monitor 是线程私有的数据结构,每个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每个被锁住的对象都会和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段存放拥有该锁的线程的惟一标识,表示该锁被这个线程占用。其结构以下:线程
说完几个关键概念以后来讲一下锁的问题:翻译
偏向锁
偏向锁是锁的级别中最低的锁,举个例子: 在此demo中,得到操做list的一直都是main线程,没有第二个线程参与操做,此时的锁就是偏向锁,偏向锁很轻,jdk 1.6默认开启,当第一个线程进入的时候,对象头中的threadid为0,表示未偏向任何线程,也叫作匿名偏向量设计
public class SyncDemo1 { public static void main(String[] args) { SyncDemo1 syncDemo1 = new SyncDemo1(); for (int i = 0; i < 100; i++) { syncDemo1.addString("test:" + i); } } private List<String> list = new ArrayList<>(); public synchronized void addString(String s) { list.add(s); } }
当第一个线程进入的时候发现是匿名偏向状态,则会用cas指令把mark words中的threadid替换为当前线程的id若是替换成功,则证实成功拿到锁,失败则锁膨胀;
当线程第二次进入同步块时,若是发现线程id和对象头中的偏向线程id一致,则通过一些比较以后,在当前线程栈的lock record中添加一个空的Displaced Mark Word,因为操做的是私有线程栈,因此不须要cas操做,synchronized带来的开销基本能够忽略;
当其余线程进入同步块中时,发现偏向线程不是当前线程,则进入到撤销偏向锁的逻辑,当达到全局安全点时,锁开始膨胀为轻量级锁,原来的线程仍然持有锁,若是发现偏向线程挂了,那么就把对象的头改成无锁状态,锁膨胀
当锁膨胀为轻量级锁时,首先判断是否有线程持有锁(判断mark work),若是是,则在当前线程栈中建立一个lock record 复制mark word 而且cas的把当前线程栈的lock record 的地址放到对象头中,若是成功,则说明获取到轻量级锁,若是失败,则说明锁已经被占用了,此时记录线程的重入次数(把lock record 的mark word 设置为null),锁会自旋能够进行自适应性自旋,确保在竞争不激烈的状况下仍然能够不膨胀为重量级锁从而减小消耗,若是cas失败,则说明线程出现竞争,须要膨胀为重量级的锁,代码以下:
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) { markOop mark = obj->mark(); assert(!mark->has_bias_pattern(), "should not see bias pattern here"); // 若是是无锁状态 if (mark->is_neutral()) { //设置Displaced Mark Word并替换对象头的mark word lock->set_displaced_header(mark); if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) { TEVENT (slow_enter: release stacklock) ; return ; } } else if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) { assert(lock != mark->locker(), "must not re-lock the same lock"); assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock"); // 若是是重入,则设置Displaced Mark Word为null lock->set_displaced_header(NULL); return; } ... // 走到这一步说明已是存在多个线程竞争锁了 须要膨胀为重量级锁 lock->set_displaced_header(markOopDesc::unused_mark()); ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD); }
重量级锁就是咱们传统意义上的锁了,当线程发生竞争,锁膨胀为重量级锁,对象的mark word 指向堆中的 monitor,此时会将线程封装为一个objectwaiter对象插入到monitor中的contextList中去,而后暂停当前线程,当持有锁的线程释放线程以前,会把contextList里面的全部线程对象插入到EntryList中去,会从EntryList中挑选一个线程唤醒,被选中的线程叫作Heir presumptive即假定继承人(应该是这样翻译),就是图中的Ready Thread,假定继承人被唤醒后会尝试得到锁,但synchronized是非公平的,因此假定继承人不必定能得到锁(这也是它叫"假定"继承人的缘由)。
若是线程得到锁后调用Object#wait方法,则会将线程加入到WaitSet中,当被Object#notify唤醒后,会将线程从WaitSet移动到cxq或EntryList中去。须要注意的是,当调用一个锁对象的wait或notify方法时,如当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。