文章目录
synchronized的局限性
synchronized是java内置的关键字,它提供了一种独占的加锁方式。synchronized的获取和释放锁由jvm实现,用户不需要显示的释放锁,非常方便,然而synchronized也有一定的局限性,
例如:
- 当线程尝试获取锁的时候,如果获取不到锁会一直阻塞,这个阻塞的过程,用户无法控制
- 如果获取锁的线程进入休眠或者阻塞,除非当前线程异常,否则其他线程尝试获取锁必须一直等待
JDK1.5之后发布,加入了Doug Lea(并发包大神Doug Lea)
实现的java.util.concurrent包。包内提供了Lock类,用来提供更多扩展的加锁功能。Lock弥补了synchronized的局限,提供了更加细粒度的加锁功能。
ReentrantLock
ReentrantLock是Lock的默认实现,在聊ReentranLock之前,我们需要先弄清楚一些概念:
- 可重入锁:可重入锁是指同一个线程可以多次获得同一把锁;
ReentrantLock和关键字Synchronized都是可重入锁
- 可中断锁:可中断锁时只线程在获取锁的过程中,是否可以相应线程中断操作。
synchronized是不可中断的,ReentrantLock是可中断的
- 公平锁和非公平锁:公平锁是指多个线程尝试获取同一把锁的时候,获取锁的顺序按照线程到达的先后顺序获取,而不是随机插队的方式获取。
synchronized是非公平锁,而ReentrantLock是两种都可以实现,不过默认是非公平锁
注意
- lock()方法和unlock()方法需要成对出现,锁了几次,也要释放几次,否则后面的线程无法获取锁了;
- unlock()方法放在finally中执行,保证不管程序是否有异常,锁必定会释放
ReentrantLock实现公平锁
在大多数情况下,锁的申请都是非公平的,也就是说,线程1首先请求锁A,接着线程2也请求了锁A。那么当锁A可用时,是线程1可获得锁还是线程2可获得锁呢?这是不一定的,系统只是会从这个锁的等待队列中随机挑选一个,因此不能保证其公平性.
而公平锁,则不是这样,它会按照到达的先后顺序获得资源。公平锁的一大特点是:它不会产生饥饿现象,只要你排队,最终还是可以等到资源的;synchronized关键字默认是有jvm内部实现控制的,是非公平锁。而ReentrantLock运行开发者自己设置锁的公平性。
//默认构造方法创建的是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
第2个构造方法,有个fair参数,当fair为true的时候创建的是公平锁
,公平锁看起来很不错,不过要实现公平锁,系统内部肯定需要维护一个有序队列,因此公平锁的实现成本比较高,性能相对于非公平锁来说相对低一些
。因此,在默认情况下,锁是非公平的,如果没有特别要求,则不建议使用公平锁。
ReentrantLock获取锁的过程是可中断的
对于synchronized关键字
,如果一个线程在等待获取锁,最终只有2种结果:
- 要么获取到锁然后继续后面的操作
- 要么一直等待,直到其他线程释放锁为止
而ReentrantLock提供了另外一种可能,就是在等的获取锁的过程中(发起获取锁请求到还未获取到锁这段时间内)是可以被中断的,也就是说在等待锁的过程中,程序可以根据需要取消获取锁的请求
。有些使用这个操作是非常有必要的。比如:你和好朋友越好一起去打球,如果你等了半小时朋友还没到,突然你接到一个电话,朋友由于突发状况,不能来了,那么你一定达到回府。中断操作正是提供了一套类似的机制,如果一个线程正在等待获取锁,那么它依然可以收到一个通知,被告知无需等待,可以停止工作了
ReentrantLock锁申请等待限时
申请锁等待限时是什么意思?一般情况下,获取锁的时间我们是不知道的,synchronized关键字获取锁的过程中,只能等待其他线程把锁释放之后才能够有机会获取到所。所以获取锁的时间有长有短。如果获取锁的时间能够设置超时时间,那就非常好了。
ReentrantLock刚好提供了这样功能,给我们提供了获取锁限时等待的方法 tryLock(),可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败。
总结
- ReentrantLock可以实现公平锁和非公平锁
- ReentrantLock默认实现的是非公平锁
- ReentrantLock的获取锁和释放锁必须成对出现,锁了几次,也要释放几次
- 释放锁的操作必须放在finally中执行
- lockInterruptibly()实例方法可以相应线程的中断方法,调用线程的interrupt()方法时,lockInterruptibly()方法会触发 InterruptedException异常
- isHeldByCurrentThread:实例方法,判断当前线程是否持有ReentrantLock的锁
- 实例方法 tryLock()是立即响应的,中间不会有阻塞
- 实例方法tryLock(long timeout, TimeUnit unit)会在指定的时间内尝试获取锁,指定的时间内是否能够获取锁,都会返回,返回true,表示获取锁成功,返回false表示获取失败,该方法会响应线程的中断