javas的四种状态 无锁状态 偏向锁状态 轻量级锁状态 重量级锁状态

一:java多线程互斥,和java多线程引入偏向锁和轻量级锁的缘由?

--->synchronized是在jvm层面实现同步的一种机制。 
  jvm规范中能够看到synchronized在jvm里实现原理,jvm基于进入和退出Monitor对象来实现方法同步和代码块同的。在代码同步的开始位置织入monitorenter,在结束同步的位置(正常结束和异常结束处)织入monitorexit指令实现。线程执行到monitorenter处,将会获取锁对象对应的monitor的全部权,即尝试得到对象的锁。(任意对象都有一个monitor与之关联,当且一个monitor被持有后,他处于锁定状态).
  在线程运行到该代码块的时候,让程序的运行级别从用户态切换到内核态,把线程挂起,让cpu经过操做系统指令,去调度多线程之间,谁执行代码块,谁进入阻塞状态。这样会频繁出现程序运行状态的切换,这样就会大量消耗资源,程序运行的效率低下。缘由是monitorenter与monitorexit这两个控制多线程同步的bytecode原语,是jvm依赖操做系统互斥(mutex)来实现的。
  为了优化synchronized机制,从java6开始引入偏向锁,和轻量级锁,尽可能让多线程访问公共资源的时候,不进行程序运行状态的切换。轻量级锁本意是为了减小多线程进入互斥的概率,并非要替代互斥。它利用了cpu原语Compare-And-Swap(cas,汇编指令CMPXCHG),尝试进入互斥前,进行补救。

二:为何要自旋或者自适应自旋?

--->前面咱们讨论互斥同步的时候,提到了互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操做都须要转入内核态中完成,这些操做给系统的并发性能 带来了很大的压力。同时,虚拟机的开发团队也注意到在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。若是物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,咱们就可让后面请求锁的那个线程“稍等一会”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,咱们只须让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。 
 
--->自旋锁在JDK1.4.2中就已经引入,只不过默认是关闭的,可使用-XX:+UseSpinning参数来开启,在JDK 1.6中就已经改成默认开启了。自旋等待不能代替阻塞,且先不说对处理器数量的要求,自旋等待自己虽然避免了线程切换的开销,但它是要占用处理器时间的, 因此若是锁被占用的时间很短,自旋等待的效果就会很是好,反之若是锁被占用的时间很长,那么自旋的线程只会白白消耗处理器资源,而不会作任何有用的工做, 反而会带来性能的浪费。所以自旋等待的时间必需要有必定的限度,若是自旋超过了限定的次数仍然没有成功得到锁,就应当使用传统的方式去挂起线程了。自旋次 数的默认值是10次,用户可使用参数-XX:PreBlockSpin来更改。 
 
--->在JDK 1.6中引入了自适应的自旋锁。自适应意味着自旋的时间再也不固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。若是在同一个锁对象 上,自旋等待刚刚成功得到过锁,而且持有锁的线程正在运行中,那么虚拟机就会认为此次自旋也颇有可能再次成功,进而它将容许自旋等待持续相对更长的时间, 好比100个循环。另外一方面,若是对于某个锁,自旋不多成功得到过,那在之后要获取这个锁时将可能省略掉自旋过程,以免浪费处理器资源。有了自适应自 旋,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的情况预测就会愈来愈准确,虚拟机就会变得愈来愈“聪明”了。 
 
 

三:锁削除

--->锁削除是指虚拟机即时编译器在运行时,对一些代码上要求同步,可是被检测到不可能存在共享数据竞争的锁进行削除。锁削除的主要断定依据来源于逃逸分析的数据支持,若是判断到一段代码中,在堆上的全部数据都不会逃逸出去被其余线程访问到,那就能够把它们看成栈上数据对待, 认为它们是线程私有的,同步加锁天然就无须进行。好比(只是说明概念,但实际状况并不必定如例子)在线程安全的环境中使用stringBuffer进行字符串拼加。则会在java文件编译的时候,进行锁销除。
  

四:锁粗化

原则上,咱们在编写代码的时候,老是推荐将同步块的做用范围限制得尽可能小——只在共享数据的实际做用域中才进行同步,这样是为了使得须要同步的操做数量尽量变小,若是存在锁竞争,那等待锁的线程也能尽快地拿到锁。大部分状况下,上面的原则都是正确的,可是若是一系列的连续操做都对同一个对象反复加锁和解锁,甚至加锁操做是出如今循环体中的,那即便没有线程竞争,频繁地进行互斥同步操做也会致使没必要要的性能损耗。若是虚拟机探测到有这样一串零碎的操做都对同一个对象加锁,将会把加锁同步的范围扩展(锁粗化)到整个操做序列的外部。
 

五:偏向锁,轻量级锁,重量级锁对比

 
优势 缺点 适用场景
偏向锁 加锁和解锁不须要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距 若是线程间存在锁竞争,会带来额外的锁撤销的消耗 适用于只有一个线程访问同步块场景
轻量级锁 竞争的线程不会阻塞,提升了程序的响应速度 若是始终得不到索竞争的线程,使用自旋会消耗CPU 追求响应速度,同步块执行速度很是快
重量级锁 线程竞争不使用自旋,不会消耗CPU 线程阻塞,响应时间缓慢 追求吞吐量,同步块执行速度较慢
 
任何一个java对象的头部(monitor)包括:
 
长度 内容 说明
32/64bit Mark Word 存储对象的hashcode或锁信息
32/64bit 类对象的地址 存储到对象类型数据的指针
32/64bit Array length 数组的长度(若是当前对象是数组)
其中Mark Word存储内容以下

 

六:锁的状态

--->锁一共有四种状态(根据重量级由低到高的次序):无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态
--->锁的等级只能够升级,不能够降级。这种锁升级却不能降级的策略,目的是为了提升得到锁和释放锁的效率。

七:偏向锁

大多数状况下,锁不只不存在多线程竞争,并且老是由同一线程屡次得到,为了让线程得到锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头Mark Wod栈帧锁记录里存储锁偏向的线程ID,之后该线程在进入和退出同步块时不须要进行CAS操做来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。java

  • 若是测试成功,表示线程已经得到了锁。
  • 若是测试失败,则测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):若是没设置,则使用CAS竞争锁(竞争什么?);若是设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
--->a线程得到锁,会在a线程的的栈帧里建立lockRecord,在lockRecord里和锁对象的MarkWord里存储线程a的线程id.之后该线程的进入,就不须要cas操做,只须要判断是不是当前线程。
--->a线程获取锁,不会释放锁。直到b线程也要竞争该锁时,a线程才会释放锁。
--->偏向锁的释放,须要等待全局安全点(在这个时间点上没有正在执行的字节码),它会首先暂停拥有偏向锁的线程,而后检查持有偏向锁的线程是否还活着,若是线程仍然活着,拥有偏向锁的栈会被执行。若是线程不处于活动状态,则将锁对象的MarkWord设置成无锁状态。栈帧中的lockRecord和对象头的MarkWord要么从新偏向其余线程,要么恢复到无锁,或者标记对象不适合做为偏向锁(锁升级)。最后唤醒暂停的线程。
--->关闭偏向锁,经过jvm的参数-XX:UseBiasedLocking=false,则默认会进入轻量级锁。
 
偏向锁升级:一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它如今认为只可能有一个线程来访问它,因此当第一个线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第一个线程,这个线程在修改对象头MarkWord成为偏向锁的时候使用CAS操做,并将对象头中的ThreadID改为本身的ID,以后再次访问这个对象时,只须要对比ID,不须要再使用CAS在进行操做。一旦有第二个线程访问这个对象,由于偏向锁不会主动释放,因此第二个线程能够看到对象时偏向状态,这时代表在这个对象上已经存在竞争了,操做系统检查原来持有该对象锁的线程是否依然存活,若是挂了,则能够将对象变为无锁状态,而后从新偏向新的线程,若是原来的线程依然存活,则立刻执行那个线程的操做栈,检查该对象的使用状况,若是仍然须要持有偏向锁,则偏向锁升级为轻量级锁(偏向锁就是这个时候升级为轻量级锁的)。若是不存在使用了,则能够将对象回复成无锁状态,而后从新偏向。
 

八:轻量级锁

轻量级锁加锁:线程在执行同步块以前,JVM会将 对象头中的Mark Word复制到 栈帧锁记录中,官方称为Displaced Mark Word。而后线程尝试使用CAS将对 象头中的Mark Word替换为指向 栈帧锁记录的指针。若是成功,当前线程得到锁,若是失败,表示其余线程竞争锁,当前线程便尝试使用自旋来获取锁。
轻量级锁解锁:轻量级解锁时,会使用原子的CAS操做将Displaced Mark Word替换回到对象头,若是成功,则表示没有竞争发生。若是失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。
 
--->a线程得到锁, 栈帧锁记录中的lock record指针指向 锁对象的对象头mark word.再让mark word 指向lock record.这就是获取了锁。
--->轻量级锁,b线程在锁竞争时,发现锁已经被a线程占用,则b线程不进入内核态,让b线程自旋,执行空循环,等待a线程释放锁。若是,完成自旋策略仍是发现a线程没有释放锁,或者让c线程占用了。则b线程试图将轻量级锁升级为重量级锁。
 
 
轻量级锁认为竞争存在,可是竞争的程度很轻,通常两个线程对于同一个锁的操做都会错开,或者说稍微等待一下(自旋),另外一个线程就会释放锁。 可是当自旋超过必定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程之外的线程都阻塞,防止CPU空转。
 

九:重量级锁

就是让争抢锁的线程从用户态转换成内核态。让cpu借助操做系统进行线程协调。数组

相关文章
相关标签/搜索