Lock
Lock继synchronized出现的原因
synchronized无法实现破坏不可抢占锁的功能,synchronized申请资源申请不到的时候,直接进入阻塞状态,啥都干不了,也释放不了线程占有的资源;
所以为了解决这个问题,出现了Java SDK——Lock;Lock接口的三个方法如下:
// 支持中断的API
// 能够响应中断。阻塞状态的线程能够响应中断信号,能够唤醒它(那么就有可能释放持有的锁)
void lockInterruptibly() throws InterruptedException;
// 支持超时的API
// 一段时间内没有获取到锁,不是进入阻塞状态,而是返回错误
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 支持非阻塞获取锁的API
// 尝试获取锁失败,不进入阻塞状态直接返回
boolean tryLock();
如何保证可见性
synchronized之所以能保证可见性是因为Happens-Before中一条规则,”管程中锁的规则。对一个锁的解锁Happens-Before于后续对这个锁的加锁“;
Java SDK里面的锁主要是利用violate相关的Happens-Before规则(顺序性、volatile变量规则、传递性规则),例子如下:
class SampleLock {
volatile int state;
// 加锁
lock() {
// 省略代码无数
state = 1;
}
// 解锁
unlock() {
// 省略代码无数
state = 0;
}
}
可重入锁
ReentrantLock,线程可以重复获取同一把锁;除了可重入锁还有可重入函数,可重入函数指得是多个线程同时调用该函数,每个线程都能得到正确结果;同时在一个线程内支持线程切换。其实就是意味着线程安全;
公平锁和非公平锁
ReentrantLock 这个类有两个构造函数,一个是无参构造函数,一个是传入 fair 参数的构造函数。
//无参构造函数:默认非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//根据公平策略参数创建锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
实战示例
利用两个条件变量快速实现阻塞队列
public class BlockedQueue<T>{
final Lock lock = new ReentrantLock();
// 条件变量:队列不满
final Condition notFull = lock.newCondition();
// 条件变量:队列不空
final Condition notEmpty = lock.newCondition();
// 入队
void enq(T x) {
lock.lock();
try {
while (队列已满){
// 等待队列不满
notFull.await();
}
// 省略入队操作...
//入队后,通知可出队
notEmpty.signal();
} finally {
lock.unlock();
}
}
// 出队
void deq(){
lock.lock();
try {
while (队列已空){
// 等待队列不空
notEmpty.await();
}
// 省略出队操作...
//出队后,通知可入队
notFull.signal();
} finally {
lock.unlock();
}
}
}