在博文多线程之内存可见性Volatile(一),多线程之原子变量CAS算法(二)中,我介绍了如何安全的访问共享对象,给了两种解决方案,java5.0之后,增加了lock接口的高级功能。
这篇博文,我们介绍lock接口的一种实现,重入锁ReentrantLock,我们只是简单的介绍和synchronized进行一下对比。对于重入锁更加深入的内容,后面我们会再详细的写。
ReentrantLock
ReentrantLock是一个可重入的互斥锁,重入锁是一种递归无阻塞的同步机制。即ReentrantLock实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。但相较于synchronized提供了更高的处理锁的灵活性。
可重入概念
若一个程序或子程序可以“安全的被并行执行(Parallel computing)”,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,可以再次进入并执行它(并行执行时,个别的执行结果,都符合设计时的预期)。可重入概念是在单线程操作系统的时代提出的。
重入锁的构造方法提供了一个可选的公平参数:
不公平锁与公平锁的区别:
- 公平情况下,操作会排一个队按顺序执行,来保证执行顺序。(会消耗更多的时间来排队)
- 不公平情况下,是无序状态允许插队,jvm会自动计算如何处理更快速来调度插队。(如果不关心顺序,这个速度会更快)
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock与synchronized的比较
相同:ReentrantLock提供了synchronized类似的功能和内存语义。
不同:
(1)ReentrantLock功能性方面更全面,比如时间锁等候,可中断锁等候,锁投票等,因此更有扩展性。在多个条件变量和高度竞争锁的地方,用ReentrantLock更合适,ReentrantLock还提供了Condition,对线程的等待和唤醒等操作更加灵活,一个ReentrantLock可以有多个Condition实例,所以更有扩展性。
(2)ReentrantLock 的性能比synchronized会好点。
(3)ReentrantLock提供了可轮询的锁请求,他可以尝试的去取得锁,如果取得成功则继续处理,取得不成功,可以等下次运行的时候处理,所以不容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁。
实现多个客户端同时售票功能
我们使用三个线程,同时售卖20张车票,代码如下:
public class TestLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket,"1号窗口").start();
new Thread(ticket,"2号窗口").start();
new Thread(ticket,"3号窗口").start();
}
}
class Ticket implements Runnable{
private int tick = 20;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
lock.lock();//如果被其它资源锁定,会在此等待锁释放,达到暂停的效果
if(tick > 0){
try {
try {
Thread.sleep(200);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + "完成售票,余票为:" + --tick);
} finally {
lock.unlock();
}
}
}
}
}
注意:
(1) lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally 块中释放锁,可能会在程序中留下一个定时炸弹,当有一天炸弹爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放
总结
重入锁,还有很多的特性,还需要我们更加深入的学习,继续加油吧。