Lock锁框架
Lock锁接口继承关系
这是从idea导出的 java.util.concurrent.locks包下的类的接口组织关系。
其中,绿色虚线表示的是接口实现,
红色实线表示的是内部类
蓝色实现表示的是继承关系
ReetrantLock
ReentranLock实现了Lock接口,是一种可重入 的独占锁,它具有synchronized相同的功能,但是比synchronized功能更强大。
ReentrantLock内部是通过AQS框架来实现独占锁的功能的。后面我会将ReentrantLock与synchronized进行详细的对比,敬请期待。
ReentrantLock类与synchronized还有一点是,synchronized是非公平锁,而ReentrantLock可以通过构造函数中的参数来指定公平策略还是非公平策略,默认为非公平策略。
那么什么是非公平锁?什么是公平锁呢?
公平策略:在多个线程竞争锁的情况下,公平策略倾向于将访问权限授予等待时间最长的线程。也就是相当于有一个线程等待队列,“先来后到”原则,对于每个线程都是公平的。
非公平策略:在多个线程竞争锁的情况下,能够最终获得锁的线程是随机的(有OS底层调度)。
注:一般情况下,使用公平策略的程序在多线程访问时,总的吞吐量比较低,因此此时在线程调度上面的开销比较大。因为从挂起->唤醒是需要时间的,当锁被释放时,如果使用公平锁,下一个获取锁的线程需要经历挂起->唤醒这一段时间,只有等唤醒之后才能获得锁;而使用非公平锁的话,锁会被其他已经唤醒的线程立马被获取。因此,当线程持有锁的时间相对较长或者线程请求锁的平均时间间隔较长时,可以考虑使用公平策略。此时线程调度产生的耗时间隔影响会较小。
ReetrantLock使用
public class ReentrantLockTest {
ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock();
try {
//method body
} finally {
lock.unlock(); //一定要记得释放锁
}
}
}
ReetrantLock类原理
ReentrantReadWriteLock
ReentrantReadWriteLock类,顾名思义,是一种读写锁,它是ReadWriteLock接口的直接实现,该类在内部实现了具体独占锁特点的写锁,以及具有共享锁特点的读锁,和ReentrantLock一样,ReentrantReadWriteLock类也是通过定义内部类实现AQS框架的API来实现独占/共享的功能。
ReentrantReadWriteLock特点
1、支持公平/非公平策略
与ReentrantLock一样,可以通过构造函数去指定是公平还是非公平。
2、支持可重入
- 同一读线程在获取读锁后还可以获取读锁
- 同一写线程在获取写锁之后既可以再次获取写锁,也可以再次获取读锁
3、支持锁降级
所谓锁降级,就是:先获取写锁,然后获取读锁,最后释放写锁,这样写锁就降级成了读锁。但是,读锁不能升级到写锁。简言之,就是:写锁可以降级成读锁,读锁不能升级成写锁。
ReentrantReadWriteLock的使用
以下是Oracle官方给出的一个例子:
使用ReentrantReadWriteLock控制对TreeMap的访问(利用读锁控制读操作的访问,利用写锁控制修改操作的访问),将TreeMap包装成一个线程安全的集合,并且利用了读写锁的特性来提高并发访问。
public class RWTreeMap {
private final Map<String, Data> m = new TreeMap<String, Data>();
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
public Data get(String key) {
r.lock();
try {
return m.get(key);
} finally {
r.unlock();
}
}
public String[] allKeys() {
r.lock();
try {
return (String[]) m.keySet().toArray();
} finally {
r.unlock();
}
}
public Data put(String key, Data value) {
w.lock();
try {
return m.put(key, value);
} finally {
w.unlock();
}
}
public void clear() {
w.lock();
try {
m.clear();
} finally {
w.unlock();
}
}
}
StampedLock
StampedLock类是在JDK1.8时引入的,是对读写锁ReentrantReadWriteLock的增强,该类提供了一些功能,优化了读锁、写锁的访问,同时使读写锁之间还可以互相转换,更加细度的控制并发。
对于StampedLock的详细讲解详见文
LockSupport
LockSupport类,是JUC包中的一个工具类,是用来创建锁和其他同步类的基本线程阻塞原语。LockSupport类的核心方法其实就两个:park()和unpark(),其中park()方法用来阻塞当前调用线程,unpark()方法用于唤醒指定线程。这其实和Object类的wait()和signal()方法有些类似,但是LockSupport的这两种方法从语意上讲比Object类的方法更清晰,而且可以针对指定线程进行阻塞和唤醒。
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。初始时,permit为0,当调用unpark()方法时,线程的permit加1,当调用park()方法时,如果permit为0,则调用线程进入阻塞状态。
LockSupport的使用过程中还需要注意以下几点:
- park方法的调用一般要方法一个循环判断体里面。
- park方法是会响应中断的,但是不会抛出异常。(也就是说如果当前调用线程被中断,则会立即返回但不会抛出中断异常)
- park的重载方法park(Object blocker),会传入一个blocker对象,所谓Blocker对象,其实就是当前线程调用时所在调用对象(如上述示例中的FIFOMutex对象)。该对象一般供监视、诊断工具确定线程受阻塞的原因时使用。