1.java中锁体系
- 乐观锁,悲观锁
- 读锁(共享锁),写锁(排他锁)
- 自旋锁,非自旋锁
- 无锁,偏向锁,轻量级锁,重量级锁
- 分布式锁
- 区间锁(分段锁)java.util.concurrent ConcurrentHashMap
- 重入锁,非重入锁
- 公平锁,非公平锁
2.乐观锁,悲观锁
- 悲观锁:悲观的认为自己在使用数据时一定有别的线程来修改数据,在获取数据时会先加锁,确保数据不会被别的线程修改
锁实现:关键字synchronized、接口Lock的实现类
适用场景:写操作较多,先加所可以保证写操作时数据正确 - 乐观锁:乐观的认为自己在使用数据时不会有别的线程修改数据,所以会加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据
锁实现:CAS算法,例如AtomicInteger类的原子自增是通过CAS自旋实现
适用场景:读操作较多,不加锁的特点能够使其读操作的性能大幅提升 - CAS算法
Compare And Swap(比较与交换)
无锁算法:基于硬件源语,在不使用锁(没有线程阻塞)的情况下实现多线程之间的变量同步
JDK中实现:java.util.concurrent包中原子类就是通过CAS来实现了乐观锁
算法涉及到三个操作数:
需要读写的内存值 V
进行比较的值 A
要写入的新值 B
- CAS存在的问题?
- ABA问题?解决:AtomicStampedReference在变量前面添加版本号,每次变量更新时把版本号加1
- 循环时间长开销大
- 只能保证一个共享变量的原子操作?解决:AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作
3.自旋锁
- 概念:当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,自旋直到获取到锁才会退出循环
- 自旋锁存在的意义和使用场景
1.阻塞与唤醒线程需要操作系统切换cpu状态,需要消耗一定时间
2.同步代码块逻辑简单,执行时间短 - 分类
- 自旋锁
- 自适应自旋锁
自适应自旋锁假定不同线程持有同一个锁对象的时间基本相当,竞争度趋于稳定,因此可以根据上一次自旋的时间与结果调整下一次自旋的时间
jdk6通过-XX:-UseSpinning参数关闭自旋锁优化;-XX:PreBlockSpin参数修改默认的自旋次数
jdk>=7自旋锁参数被取消,虚拟机不再支持用户配置自旋锁,自旋锁总是会执行,自旋锁次数由虚拟机自动调整。
3.1进程与线程上下文切换为什么耗资源原理解析
多个cpu共用一个寄存器
4. synchronized ,悲观锁
- synchronized使用方式
- 同步实例方法,锁是当前实例对象
- 同步类方法,锁是当前类对象
- 同步代码块,锁是括号里的对象
- synchronized实现方式
synchronized是JVM内置锁,通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统Mutex lock(互斥锁)实现
- Monitor
- JVM内置锁的膨胀升级
JDK>1.6中synchronized的实现进行了优化,如适应性自旋 、锁消除、锁粗化、偏向锁、轻量级锁
- JVM对象加锁原理
对象的内存结构:
1.对象头:比如hash码,对象所属的年代,对象锁,锁状态标志,偏向锁(线程)ID,偏向时间,数组长度(数组对象)等
2.对象实际数据:即创建对象时,对象中成员变量,方法等
ps: Q:实例对象时怎么存储的?
A:对象地实例存储在堆空间,对象地元数据存在元空间,对象的引用存在栈空间。
- 锁状态
Mark Word在32位JVM中存储内容为例
5. AQS原理(AbstractQueuedSynchronizer源码)
ReentrantLock为例
ps:
隐式锁(synchronized,基于JVM的内置锁),加锁与解锁的过程不需要我们在代码中人为地控制,jvm会自动去加锁和解锁
显式锁:ReentrantLock,可重入锁,整个加锁跟解锁过程需要手动编写代码去控制
- AbstractQueuedSynchronizer:抽象队列同步器,基于CLH队列实现
6. AQS中的CLH队列,条件队列(公平锁)
- CLH队列是Craig、Landin、Hagersten三人发明的一种基于双向链表数据结构的队列,java中的CLH队列是元CLH队列的一个变种,线程由原来的的自旋机制改为阻塞机制,所以ReentrantLock是悲观锁
CLH入队时用自旋