目录
三、synchronized和ReentrantLock的区别
一、synchronized
实现方式
- 修饰实例方法:对实例的对象进行加锁
- 修饰类的方法:对类对象进行加锁
- 修饰代码块:锁括号里面的对象进行加锁
private Object object;
public void test(){
//对object对象进行加锁
synchronized(object){
//一大堆代码
}
}
底层原理
- synchronized是通过对象内部的一个叫作监视器(monitor)来实现的
- synchronized也实现了可重入的功能
一个Object对象里面包含一个Monitor监视器对象,监视器里包含一个计数器,如果计数器为0,则表示当前对象没有被任何线程所占有。也可以实现可重入的功能,已经获得了锁对象的线程可以重复加锁,先判度自己是否已经持有了这把锁,持有的话计数器增加为2.每次释放锁需要将计数器的值减1.
- 监视器锁本质是依赖于底层的操作系统Mutex Lock来实现的
- Mutext Lock指令的调用需要从用户态转换到核心态,成本非常高,因此造成synchronized效率非常低,也称其为”重量级锁”。
- JDK对synchronize进行了优化,引入了膨胀的过程:
无锁 -》 偏向锁 -》轻量级锁-》重量级锁
二、Lock接口
可重入锁ReentrantLock
- 基于AQS(抽象队列同步器),加锁机制的一种实现方式
- 实现可重入的功能:可以重复地获取自己所拥有的锁
ReentrantLock lock = new ReentrantLock(true);
try{
lock.lock();
//一大堆代码
}finally{
lock.unlock();
}
ReentrantLock的原理
- 锁内部有两个核心参数
- state:计数器
- exclusiveOwnerThread:所有者线程
- 当计数值为0时,这个锁就被认为是没有被任务线程所占有的
- 当线程请求一个未被持有的锁时,计数值将会递增
而当线程退出同步代码时,计数器会相应地递减。当计数值为0时,则释放该锁。
ReentrantLock竞争锁的过程
源代码解读
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread(); //得到当前线程
int c = getState();//得到计数器的值
if (c == 0) {//如果为0,表示锁没有被任务线程持有
if (!hasQueuedPredecessors() && //判断自己前面是否有其他线程在等待
compareAndSetState(0, acquires)) {//是队首元素,利用CAS机制去获取锁
//既是队首,同时通过CAS也成功了,锁占有成功了
setExclusiveOwnerThread(current);//
return true;
}
}
//判断当前线程是否已经占有了这把锁
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//被其他非本身线程所占有
return false;
}
ReentrantLock可重入锁的过程
ReentrantLock释放锁的过程
三、synchronized和ReentrantLock的区别
synchronized | ReentrantLock |
非公平锁:不意味着等待时间越长,越有机会得到这把锁 | 可以指定公平锁还是非公平锁 因为底层是通过AQS,内部会维护一个等待的队列,借助等待队列,可以实现这个公平锁的方式。队首的元素会优先被唤醒,会优先地去获取到锁。 |
无:其他未获取到锁的线程都处于阻塞的状态,没有机制去通知这些线程停止等待。 | 提供中断等待锁的线程的机制,可以通知等待的线程停止等待。 |
粒度较粗 | 粒度更细 tryLock() tryAcquire() , lock()等多种方法,更加灵活 |
编译器保证释放锁 | 手动主动释放锁 |
四、总结
- synchronized通过monitor和mutex lock实现了互斥
- Lock接口基于AQS(抽象队列同步器)实现了锁的获取、可重入、释放