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的实例都在申请同一个重入锁,但控制台打印就类似