裸泳的猪

沾沾自喜其实最可悲

0%

synchronized 和 ReentrantLock

经典面试题:

synchronized 和 ReentrantLock 区别是什么?

相同点:

它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的。可重入锁。可重入锁是指同一个线程可以多次获取同一把锁。ReentrantLock和synchronized都是可重入锁。

区别:

  1. synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果
  2. synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间
  3. synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁
  4. synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll();ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法
  5. synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现
  6. synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁,需要在 finally{} 代码块显示释放
  7. 锁的细粒度和灵活度,很明显ReenTrantLock优于Synchronized。

ReentrantLock主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似。

  • AQS(AbstractQueuedSynchronizer)抽象的队列式的同步器。是一个用于构建锁和同步容器的框架。事实上concurrent包内许多类都是基于AQS构建。
    AQS使用一个FIFO的队列表示排队等待锁的线程,队列头节点称作“哨兵节点”或者“哑节点”,它不与任何线程关联。其他的节点与等待线程关联,每个节点维护一个等待状态waitStatus

ReentrantLock的基本实现可以概括为:先通过CAS尝试获取锁。如果此时已经有线程占据了锁,那就加入AQS队列并且被挂起。当锁被释放之后,排在CLH队列队首的线程会被唤醒,然后CAS再次尝试获取锁。在这个时候,如果:

非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取;

公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁。

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
private Lock lock = new ReentrantLock();

public void test(){
lock.lock();
try{
doSomeThing();
}catch (Exception e){
// ignored
}finally {
lock.unlock();
}
}

ReentrantLock原理

-------------本文结束感谢您的阅读-------------