实现基础:Java的任意对象都可以作为锁。
作用方法 | 锁对象 | 实现机制 |
---|---|---|
普通方法 | 锁是类的实例对象 | 方法修饰符ACC_SYNCHRONIZED |
静态方法 | 锁是类对象 | 方法修饰符ACC_SYNCHRONIZED |
同步块 | 锁是Synchonized括号里配置的对象 | 使用monitorenter和monitorexit指令 |
Java对象头
长度 | 内容 | 说明 |
---|---|---|
32/64bit | markword | 存储对象的hashcode和锁信息 |
32/64bit | Class Metadata Address | 存储到对象类型数据的 指针 |
32/32bit | Array Length | 数组长度(如果是数组对象) |
synchronized用的锁存在Java对象头里。数组类型用3个字宽存储对象头,非数据类型用2个字宽,1字宽=4字节=32bit
32位JVM的mark word存储结构:
锁状态 | 25bit | 4bit | 1bit是否是偏向锁 | 2bit锁标志位 |
---|---|---|---|---|
无锁状态 | 对象的hashcode | 对象分代年龄 | 0 | 01 |
不同状态下的结构变化:
锁升级过程
无锁 -- 偏向锁 -- 轻量级锁 -- 重量级锁
为什么引入偏向锁:
因为大多数时候是不存在竞争的,常常是一个线程多次获得统一把锁,因此每次竞争锁会付出很多没必要的代价,而偏向锁的设计就是同一个线程再次获取锁时无需CAS加锁。
为什么引入轻量级锁:
轻量级锁考虑的是竞争线程不多,且线程持有锁时间短的情况;因为重量级锁阻塞线程需要CPU从用户态转到内核态,代价较大,假如刚阻塞不久,锁就释放了,那这个状态的转换就比程序的执行更耗时间 ,因此干脆不阻塞,让线程自旋等待锁释放。
重量级锁的重:
Java的线程是映射到操作系统原生的线程之上的,如果要阻塞或者唤醒一个线程就需要操作系统的帮忙,这就要从用户态转换到核心态,因此需要花费更多的处理器的时间,对于简单的代码,可能状态的转换消耗的时间比用户执行代码所需要的时间还要长。
synchronized与ReenterLock的对比:
1.synchronized依赖与JVM,而ReenterLock依赖于API;
2.ReenterLock可实现等待中断,可实现公平锁,可实现选择性通知。
synchronized与volatile的对比:
1.volatile是synchronized的轻量级实现,性能更好;
2.volatile保证变量可见性,不保证原子性,解决的是多个线程之间的可见性问题;synchronized可以保证原子性,解决的是多线程间资源的同步问题。
转自:《Java并发编程的艺术-方腾飞 魏鹏 陈晓明》
参考:https://blog.csdn.net/zzti_erlie/article/details/103997713