Java Lock
1. synchronized的实现原理与应用
synchronized是Java元老级锁,又被称为重量级锁,JDK1.6中为了减少获得锁和释放锁带来的性能消耗而引入偏向锁和轻量级锁
1.1 synchronized实现同步
synchronized实现同步的基础:java 中的每一个对象都可以作为锁,这些锁被称为内置锁或者监视锁。
- 对于普通同步方法,锁是当前实例对象
- 对于静态同步方法,锁是当前类的Class对象
- 对于同步方法块,锁是Synchronized括号里配置的对象
synchronized用的锁存在Java对象头里,如果对象是数组类型,则虚拟机用3个字宽存储对象头,如果对象是非数组类型,则用两个字宽存储对象头
1.2 锁的状态
JDK1.6中,锁一共有四种状态,级别从低到高依次是:
graph LR
无锁状态-->偏向锁状态
偏向锁状态-->轻量级锁状态
轻量级锁状态-->重量级锁状态
锁可以升级但是不可以降级,目的是为了提高锁和释放锁的效率
锁 | 加锁方式 | 解锁方式 | 优点 | 缺点 | 使用场景 |
---|---|---|---|---|---|
偏向锁 | 简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁 | 偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁 | 加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距 | 如果线程间存在锁竞争,会带来额外的锁撤销消耗 | 适用于只有一个线程访问同步块的场景 |
轻量级锁 | CAS | 会使用原子的CAS操作将Displaced Mark Word替换回到对象头 | 竞争的线程不会阻塞,提高了程序的响应速度 | 如果始终得不到锁竞争的线程,使用自旋会消耗CPU | 追求响应时间,同步块执行速度非常快 |
重量级锁 | 线程竞争不使用自旋,不会消耗CPU | 线程阻塞,响应时间缓慢 | 追求吞吐量,同步块执行速度较长 |
2.原子操作的实现
- 总线锁保证原子性
所谓总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占共享内存。 - 缓存锁定来保证原子性
在同一时刻,我们只需保证对某个内存地址
的操作是原子性即可,但总线锁定把CPU和内存之间的通信锁住了,这使得锁定期间,其他处
理器不能操作其他内存地址的数据,所以总线锁定的开销比较大,目前处理器在某些场合下
使用缓存锁定代替总线锁定来进行优化。
3.锁的类型
- 自旋锁
阻塞的线程不断尝试获取锁 - 公平锁
锁具有公平性,即获取锁的顺序与阻塞的顺序一致 - 独占锁
某个线程获取锁后,其他线程必须等待其释放锁 共享锁
CountDownLatch为例,锁释放时,所有在此线程上阻塞的线程都能运行读写锁为例,多个线程可以进行读操作,但是只有一个线程可以进行写操作(读共享)