说到java并发编程,就不得不说synchronized关键字,很多人称之为“重量级锁”,其实JAVASE 1.6对ynchronized进行的各种优化,synchronized并不会显得那么重了
本章要点
1.锁升级
2.synchronized
3.Lock
- AbstractQueuedSynchronizer
java中任何一个对象都可以作为锁
- synchronized修饰实例方法,锁是当前实例对象
- synchronized修饰静态方法,锁是当前类的class对象
- synchronized修饰代码块,锁是括号里面的对象
3.1锁升级
锁级别分为:无锁-->偏向锁-->轻量级锁-->重量级锁(真正意义上的加锁)
- 偏向锁(CAS操作):大多数时候是不存在锁竞争的,通常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。此时会在java对象头和栈帧中记录偏向的锁的threadID。
- 轻量级锁(自旋锁):考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景(两个线程交替访问)。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。
- 重量级锁(基于ObjectMonitor):多个线程竞争资源,竞争激烈,在操作一定次数的自旋锁(cas操作)后,还没有获得轻量级锁,锁膨胀为重量级锁,没有获取锁的线程会被阻塞。
偏向锁:
偏向锁.png
轻量级锁:
轻量级锁.png
3.2 synchronized 基本原理
synchronized是基于ObjectMonitor实现的,ObjectMonitor中有几个关键属性:
_owner:指向持有ObjectMonitor对象的线程
_WaitSet:存放处于wait状态的线程队列
_EntryList:存放处于等待锁block状态的线程队列
_recursions:锁的重入次数
_count:用来记录该线程获取锁的次数
1.只有_EntryList线列才有权利争抢锁资源 2.调研notify或者notifyAll方法,其实就是将线程从_WaitSet移到_EntryList中,然后让其去争抢锁资源
总之,被synchronized修饰的代码,在被编译器编译后在被修饰的代码前后加上了一组字节指令。代码开始位置加上monitorenter,在代码结束位置加入monitorexit,这两个字节码指令配合完成了synchronized关键字修饰代码的互斥访问。
当执行monitorenter时,若对象未被锁定时,或者当前线程已经拥有了此对象的monitor锁,则锁计数器+1,该线程获取该对象锁。
当执行monitorexit时,锁计数器-1,当计数器为0时,此对象锁就被释放了。那么其他阻塞的线程则可以请求获取该monitor锁。
3.3 Lock
Lock接口中包含的方法:
- 那获取锁:lock()、tryLock()、tryLock(long time, TimeUnit unit) 和 lockInterruptibly();
- 释放锁:unLock();
- 条件:newCondition() 返回 绑定到此 Lock 的新的 Condition 实例 ,用于线程间的协作。
ReadWriteLock锁
可重入锁:如果锁具备可重入性,当某个线程成功获取锁,调用了某个同步方法method1之后,此时如果再去调用同步方法method2,就不需要再去获取锁,而只是增加获取次数,直接去执行。
ReadWriteLock与synchronized的区别:
(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。
ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;
ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断。
(4)synchronized是非公平锁, ReentrantLock可以实现公平锁
ReentrantReadWriteLock 读写锁
读写锁:适用于读多写少的场景。
ReentrantReadWriteLock,有两把锁,即:一个是读操作相关的锁(ReadLock),称为共享锁;一个是写相关的锁(WriteLock),称为排他锁
3.4 AbstractQueuedSynchronizer(简称AQS)
AQS使用一个FIFO的队列表示排队等待锁的线程,队列头节点称作“哨兵节点”,它不与任何线程关联。其他的节点与等待线程关联,每个节点维护一个等待状态waitStatus。
image.png
ReentrantLock的基本实现可以概括为:先通过CAS(预期值:0,设置为:1)尝试获取锁,如果CAS操作成功,则将当前线程设置为exclusiveOwnerThread线程;如果CAS操作失败或已经有线程占据了锁,那就加入AQS队列并且被挂起。当锁被释放之后,排在CLH队列队首的线程会被唤醒,然后CAS再次尝试获取锁。在这个时候,如果:
非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取;
公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁
网友评论