虽然java通过synchronized可以解决线程并发的同步的问题,同时也对synchronized进行了优化,提升了性能,但是它获取锁的操作是隐式获取的,所有很多时候我们没办法更加细粒度的控制,于是就有了lock对象,帮助我们能显示的控制锁。
原理
我们一般使用lock是这样的。
1 Lock lock = new ReentrantLock(); //这里可以是自己实现Lock接口的实现类,也可以是jdk提供的同步组件
2 lock.lock();//一般不将锁的获取放在try语句块中,因为如果发生异常,在抛出异常的同时,也会导致锁的无故释放
3 try {
4 }finally {
5 lock.unlock(); //放在finally代码块中,保证锁一定会被释放
6 }
打开Lock接口,我们会发现,它基本上把加锁的整个流程进行了抽象。我们只需要按照对应的流程去完成加锁逻辑即可。
public interface Lock {
// 获取锁
void lock();
// 响应中断
void lockInterruptibly() throws InterruptedException;
// 尝试获取锁
boolean tryLock();
// 尝试获取锁,设置超时时间
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 获取等待通知组件,该组件和当前锁绑定
Condition newCondition();
然后你就会发现一个尴尬的事,我们抽象了整个加锁的流程,但是怎么拿到锁呢?总不可能还是通过synchronized去获取锁把。这里就要稍微引入另一个概念,那就是aqs,简单来说他就是一个同步器,通过一个对状态的控制保证线程并发的安全性。
也就是说Lock规范了加锁的流程,保证开发者使用每个lock的逻辑是相同的,aqs控制获取锁的逻辑,保证我们获取锁的策略的灵活性。
总结
简单理解Lock是将加锁的流程进行抽象,保证开发者加锁的流程统一,但同时将获取锁的流程通过aqs进行抽离,保证获取锁和加锁之间的独立。这样既能保证使用者,在使用不同锁的时候,流程是一致的。但是我们还可以通过不同的aqs进行组合,提供不同的加锁策略。