对于synchronized锁
实现方式
其锁类型和状态是直接存储在java对象头的Mark Word
中,锁状态的数据同步是通过JMM中的内存屏障来进行原子操作,其加锁和解锁过程直接通过底层的指令级和JVM操作对应的java对象头来实现的,且完全写死,无法对加锁和解锁过程进行自定义。
历史变化
在过去,synchronized一直是重量级锁,但是在java SE 1.6后,对该锁进行了很多优化,引入了偏向锁和轻量级锁,并建立了自动升级机制。
锁可以升级但是不能降级,目前锁有四种等级,这四种锁是针对synchronized的,不适用于Lock类型的锁。
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
jvm会根据竞争情况对锁进行升级。
偏向锁
适用情况:只有一个线程经获取用该锁,其他线程很少获取该锁。
处理方式:(底层过于复杂,这里用抽象的方式去描述)
- 线程获取该锁时直接将该锁的钥匙交给其,释放锁时不归还钥匙。之后该线程在获取该锁时只用检查钥匙即可,不再进行获取锁和释放锁的操作。
- 第二个线程获取该锁时,锁发现自己没有钥匙,就会将程序暂停下来。去寻找拿着钥匙的线程。
- 若那个线程已经不需要钥匙,则会要回钥匙并交给第二个线程。
- 多次执行2过程,会升级为轻量级锁。
轻量级锁
适用情况:竞争不激烈
特点:
- 竞争失败后通过自旋不断的获取锁。
- 释放锁时会判断该的竞争情况,视情况升级为重量级锁。
- CAS使用的也是自旋锁。
重量级锁
适用情况:竞争激烈
特点:竞争失败后线程阻塞,等待释放锁时唤醒。
对于通过实现Lock接口而建立的锁
实现方式
A -> B
表示基于A实现B。
基于JMM和内存屏障的原子操作 //java底层的多线程数据同步机制
->
适用于各种不同情况的队列同步器 //这里负责实现锁状态的同步和线程的阻塞队列
->
继承Lock接口的各种不同类型的锁 //这里负责实现不同的加锁和解锁逻辑
与synchronized最大的区别就是队列同步器和Lock锁都是通过java代码实现的,可以根据需要对其进行自定义。并且这里将复杂的同步和阻塞过程都放到了同步器中,自定义新的锁时只需要考虑加锁和解锁的逻辑即可。
历史变化
在以前,java只有synchronized这一种锁,在java SE 5之后,引入了Lock接口来实现各种各样不同类型的锁。
相关概念:
- 公平锁:按照时间顺序获取锁。
- 排它锁:一次只能有一个线程获取。
- 重入锁:一个线程可以n次获取该锁,同时也需要n次释放才能彻底将锁释放掉。
Lock接口各种实现:
- ReentrantLock:重入锁,公平锁,排它锁。
- ReentrantReadWriteLock:读写锁,重入锁,可选择是否是公平锁。
包含一对锁,分别是读锁和写锁。支持公平性选择和锁降级(自动降级为读锁或者写锁)。
- 加锁的代码块过大,会降低系统的吞吐量,应尝试拆开。
- 加锁过多,会增加获取和释放锁的开销,应尝试合并。