Java之锁实现

1、实现原理

      1.1实现原理:

       Java锁的实现(包括synchronized和wait/notify)是使用监视器锁(monitor)实现的,编译后的字节码是monitorenter和monitorexit指令,有monitorenter,就必有monitorexit。

      1.2monitorenter:

       每一个对象上都有一个monitor,当某一线程战友montior时,就处于锁定状态,获取锁的过程:

       1)如果monitor进入数为0,则该线程进入monitor,然后进入数这只为1,线程又有该monitor

       2)如果该线程已经占有monitor,则该线程重新进入monitor,进入数+1,。

       3)如果其他线程占有该monitor,则该线程处于阻塞状态,直到monitor进入数为0,再尝试获取monitor所有权。

       1.3monitorexit

        执行monintorexit必须是monitor线程的拥有者,执行指令时,monitor进入数为1,若减1后进入数为0,则线程退出monitor,其他被这个monitor阻塞的线程可以尝试获取monitor的所有权。

 

2、线程状态及转化:

      monitor由ObjectMonitor实现,其主要数据结构如下

      有两个队列,_EntryList和WaitSet,用来保证ObjectWait对象列表(每个等待锁的线程都会封装成ObjectWait)。_owner字段指向拥有ObjectMonitor的线程。

      1)当多个线程同时访问互斥区的时候,首先进入EntryList,等待锁处于阻塞状态。

      2)线程获取monitor后,进入_owner区域,并把ObjectMonitor的_owner字段修改为当前线程,同时monitor计数+1

      3)若线程获得锁之后调用wait方法,则该线程释放monitor,owner变量变为null。同时,该线程进入_waitSet队列,线程处于wait状态

      4)若当前线程执行完毕,释放monitor,并所有值复位。

      

2、使用方法:

      1)保证同一时刻只有一个线程进入临界区

      2)保证共享变量修改可见性

      3)有效解决重排序问题

3、锁优化:

      锁的状态有,无锁-》偏向锁-》轻量级锁-》重量级锁,锁可以随着竞争升级,但是不可以降级。

      3.1 要讲锁优化,就要首先了解一下java里面对象头的数据结构,对象头有两部分

      第一部分:

      第二部分:方法区对象类型的数据指针(主要存储对象类型)

      3.2重量级锁:

      monitor监视器锁本质就是操作系统Mutex Lock互斥量的实现。称之为重量级锁的原因在于OS实现加锁和解锁都需要系统调用,系统调用需要从用户态到系统态的千幻,成本较高(自己理解,与原文不一样)。重量级锁的实现见1.1。

      3.3轻量级锁:

      轻量级锁是相对于重量级锁而言,在没有多线程竞争的条件下,轻量级锁减少OS使用互斥量带来的性能的消耗。

      优化依据:对于大部分线程,在整个同步周期,不会有竞争。如果没有竞争,轻量级锁就可以通过CAS操作避免使用互斥量。

       3.4偏向锁:

       轻向锁是线程无竞争条件下,使用CAS操作消除互斥量。偏向锁是无线程竞争条件下,直接消除同步(加锁和解锁)。

        优化依据:对于大部分线程,在整个同步周期,不会有竞争,并且总有同一个线程获取该锁,偏向锁会偏向第一个持有他的线程,持有偏向锁的线程,进入互斥区不需要进行同步。

       3.5锁的比较:

     3.6 其他优化:

           1)自旋锁:

           互斥同步时,挂起和恢复线程都需要切换到内核态完成,这对性能并发带来了不少的压力。同时在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段较短的时间而去挂起和恢复线程并不值得。那么如果有多个线程同时并行执行,可以让后面请求锁的线程通过自旋(CPU忙循环执行空指令)的方式稍等一会儿,看看持有锁的线程是否会很快的释放锁,这样就不需要放弃CPU的执行时间适应性自旋

           在轻量级锁获取过程中,线程执行 CAS 操作失败时,需要通过自旋来获取重量级锁。如果锁被占的时间比较短,那么自旋等待的效果就会比较好,而如果锁占用的时间很长,自旋的线程则会白白浪费 CPU 资源。解决这个问题的最简答的办法就是:指定自旋的次数,如果在限定次数内还没获取到锁(例如10次),就按传统的方式挂起线程进入阻塞状态。JDK1.6 之后引入了自适应性自旋的方式,如果在同一锁对象上,一线程自旋等待刚刚成功获得锁,并且持有锁的线程正在运行中,那么JVM 会认为这次自旋也有可能再次成功获得锁,进而允许自旋等待相对更长的时间(例如100次)另一方面,如果某个锁自旋很少成功获得,那么以后要获得这个锁时将省略自旋过程,以避免浪费 CPU。

           2)锁消除:

           锁消除就是编译器运行时,对一些被检测到不可能存在共享数据竞争的锁进行消除。如果判断一

段代码中,堆上的数据不会逃逸出去从而被其他线程访问到,则可以把他们当做栈上的数据对待,认为它们是线程私有的,不必要加锁

           3)锁粗化:

           锁粗化就是JVM检测到一串零碎的操作都对同一个对象加锁,则会把加锁同步的范围粗化到整

个操作序列的外部。以上述 concatString() 方法为例,内部的 StringBuffer.append() 每次都会加锁,将会锁粗化,在第一次 append() 前至 最后一个 append() 后只需要加一次锁就可以了。

摘自:https://www.toutiao.com/a6543028109912834573/

猜你喜欢

转载自chenghao666.iteye.com/blog/2419252