Java 并发:重入锁 ReentrantLock 与条件锁 Condition (上篇)

Java 并发:重入锁 ReentrantLock 与条件锁 Condition (上篇)

这篇文章的起因,是因为最近有很多小伙伴表示,面试时候被问到了很多关于Java并发的问题,尤其是在Java的锁方面,大部分人工作时候接触的只有synchronized,对于Java中其它形式的锁几乎一无所知。

这个责任其实主要在于一些老程序员。像重入锁是在Java 1.5以后才支持的,但很多教材和陈年老代码,却仍然坚持着使用synchronized,并不试图去研究一些新的特性——我知道很多人会用奥卡姆剃刀原则反驳我,即“如无必要,勿增实体”,可惜的是很多时候,对于”如无必要“的判断都是值得商榷的。所以我今天想介绍一下在Java并发中,我个人比较推崇的这种可以替代synchronized的工具类。

另外,我会尽量尝试用任何人都听得懂的表述形式。

需要注意的是,得益于Java不断的发展,目前synchronized关键字和ReentrantLock类的性能差异,已经少之又少了。但是我个人认为,在代码可读性与灵活性方面,ReentrantLock还是稍胜一筹。

Why ReentrantLock?

像上面说的一样,80%的场景下,synchronized关键字可以和ReentrantLock相互替换,实现同样的功能,并且性能相差无几。但对于一些特殊的case,ReetrantLock就灵活多了。

1)synchronized实现是非公平锁的实现,而ReentrantLock可以指定公平锁或者非公平锁。

这里简单介绍一下其间的差别。大家都知道,实际上并发线程,并不一定是并行执行的,很多情况下并发的线程是由CPU分配时间片,实际上有可能是串行的,只是由于CPU分配的关系,看起来像并行执行而已。那么对于多个线程申请同一个带锁的资源,CPU要怎么分配呢?

这就取决于锁的性质了。对于公平锁而言,系统会维护一个有序队列,几乎是严格遵守”先来后到“原则,按照时间顺序分配资源。对于非公平锁,基本上就是随机挑选,所以可以产生各种插队的情况。当然,实际实现要更加复杂,但中心思想就是这样。

由于要维护执行队列,公平锁性能会远低于非公平锁,但公平锁的行为是可预测行为,在某些特定环境下公平要比效率更重要一些。

举个例子:

public class FairLock extends Thread {
  public static ReentrantLock fair = new ReentrantLock(true); // true表示公平锁
  public FairLock(String s) {
    super(s);
  }

  @Override
  public void run() {
    while (true) {
      try {
        fair.lock();
        System.out.println(Thread.currentThread()
                                 .getName() + " is running");
      } finally {
        fair.unlock();//必须在finally里释放锁
      }
    }
  }

  public static void main(String args[]) throws InterruptedException {
    new FairLock("wtf 1").start();
    new FairLock("wtf 2").start();
  }
}

上述代码中两个FairLock的实例都在申请同一个重入锁,但控制台打印就类似

top Created with Sketch.