用户态与内核态.
JDK早期,synchronized 叫做重量级锁,因为申请锁资源必须通过kernel,系统调用
; he11o. asm ;write(int fd,const void *buffer, size_ t nbytes) section data msg db "Hello", 0xA 1en equ $ -msg section .text g1obal _start _start: mov edx,1en mov ecx, msg mov ebx, 1 ; 文件描述符1 std_ _out mov eax, 4 ;write函数 系统调用号4 int 0x80 mov ebx, 0 mov eax,1 ;exit函数系统调用号 int 0x80
CAS
compare and swap
compare and exchange
ABA 问题简单理解就是你不知道你的女朋友到底经历过几次恋爱,可以通过加版本标识解决.
cas汇编实现
lock cmpchgl就能保证原子性
其实 cas,volatile,synchronize 底层实现都是 lock 指令.
对象的内存布局
锁升级
- 匿名偏向是偏向锁启动了,但还没有指定线程.
偏向锁在JDK6是默认启用的,但在应用程序启动大概 4秒后才激活
- 使用 -XX:BiasedLockingStartupDelay=0 参数关闭延迟,立即启动偏向锁
- 如果确定应用程序中所有锁通常情况下处于竞争状态,可以通过 XX:-UseBiasedLocking=false 参数关闭偏向锁
-X print help on non- standard options
java -XX:+PrintFlagsFinal -version
- 可以看到有七百多个参数
- java -XX:+PrintFlagsFinal —version (查看jvm 的可以配置的参数)
- java -XX:+PrintFlagsFinal —version grep BiasedLocking(查看jvm 的可以配置的参数)
- UseBiasedLocking 是否启动偏向级锁
我们知道synchronized是重量级锁,效率不怎么样,不过在JDK6中对synchronize的实现进行了各种优化,使得它显得不是那么重了,那么JVM采用了那些优化手段呢
锁优化
如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
锁主要存在四种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。锁状态会随着竞争的激烈而逐渐升级。
锁可以升级,但不可降级!这是为了提高获得锁和释放锁的效率。
自旋锁(轻量级锁)
突然有了线程来竞争了,就不是偏向锁了,开始升级为自旋锁。
竞争过程就是看谁能把自己的 id 信息放进 markword 里的 id 即可,通过 CAS 方式。
和偏向锁一样,无需经过os资源,只需JVM即可。
线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁阻塞和唤醒对CPU来说是一件负担很重的工作,势必会给系统的并发性能带来很大的压力。
同时我们发现在许多应用上面,对象锁的锁状态只会持续很短一段时间,为了这段很短的时间而去频繁阻塞和唤醒线程非常不值得,所以引入自旋锁。
自旋锁,就是让该线程等待一段时间,而不会被立即挂起,看持有锁的线程是否会很快释放锁。
怎么等待呢?执行一段无意义的循环即可(自旋)。
自旋等待不能替代阻塞,先不说对处理器数量的要求,虽然它可以避免线程切换带来的开销,但是它占用CPU时间。
若持有锁的线程很快就释放了锁,那么自旋效果就很好;反之白白浪费CPU资源,它不会做任何有意义的工作,典型的占着茅坑不拉屎。
所以,自旋等待的时间(自旋的次数)必须有个限度,若自旋超过了定义时间,仍未获取到锁,则应该被挂起。
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()) { // Anticipate successful CAS -- the ST of the displaced mark must 预料 CAS 成功 // be visible <= the ST performed by the CAS. lock->set_displaced_header(mark); if (mark == obj()->cas_set_mark((markOop) lock, mark)) { return; } // Fall through to inflate() ... 如果失败 =》锁膨胀 } 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"); lock->set_displaced_header(NULL); return; } // The object header will never be displaced to this lock, // so it does not matter what the value is, except that it // must be non-zero to avoid looking like a re-entrant lock, // and must not look locked either. lock->set_displaced_header(markOopDesc::unused_mark()); ObjectSynchronizer::inflate(THREAD, obj(), inflate_cause_monitor_enter)->enter(THREAD); }
若 CAS 成功就直接 return 了,若失败会执行下面的锁膨胀方法,也没看到自旋操作。
所以轻量级锁 CAS 失败,不会自旋,而是直接膨胀成重量级锁。
自旋锁在JDK 1.4.2中引入,默认关闭,但是可以使用-XX:+UseSpinning开启。
在JDK1.6中默认开启。同时自旋的默认次数为10次,可以通过参数-XX:PreBlockSpin来调整。
等待的线程超过 CPU 的一半数量,则升级为重量级锁。
若通过参数-XX:preBlockSpin来调整自旋锁的自旋次数,会带来诸多不便。假如我将参数调整为10,但是系统很多线程都是等你刚退出时,就释放了锁(假如你多自旋一两次就可以获取锁),是不是很尴尬?
于是JDK1.6引入自适应的自旋锁,让虚拟机会变得越来越聪明。