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/