JDK中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock。虽然在性能上ReentrantLock和synchronized没有什么区别,但ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。

1. synchronized与ReentrantLock的区别

  • synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。

    ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。

  • synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;

    1. **ReentrantLock**也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
  • synchronized不可响应中断,一个线程获取不到锁就一直等着

ReentrantLock可以响应中断

2. ReentrantLock实现公平锁

  • 公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁。
  • 非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取;

synchronized一样,默认的ReentrantLock实现是非公平锁,因为相比公平锁,非公平锁性能更好。当然公平锁能防止饥饿,某些情况下也很有用。在创建ReentrantLock的时候通过传进参数true创建公平锁,如果传入的是false或没传参数则创建的是非公平锁。
在实现上,是通过判断构造函数传入参数进行判断:

  1. public ReentrantLock() {
  2. // 默认非公平锁
  3. sync = new NonfairSync();
  4. }
  5. public ReentrantLock(boolean fair) {
  6. // true是公平锁,false是非公平锁
  7. sync = fair ? new FairSync() : new NonfairSync();
  8. }

非公平锁可能会造成饥饿问题,也就是说如果申请获取锁的线程足够多,那么可能会造成某些线程长时间得不到锁。 公平锁和非公平锁该如何选择?
大部分情况下我们使用非公平锁,因为其性能比公平锁好很多。但是公平锁能够避免线程饥饿,某些情况下也很有用。

3. ReentrantLock可实现中断

当使用synchronized实现锁时,阻塞在锁上的线程除非获得锁否则将一直等待下去,也就是说这种无限等待获取锁的行为无法被中断。而ReentrantLock给我们提供了一个可以响应中断的获取锁的方法lockInterruptibly()。该方法可以用来解决死锁问题。