Lock 和 synchronized

前言

Java多线程编程中,保证线程安全通常会使用到synchronizedLock。那么合适该使用synchronized,什么时候该使用Lock呢。这个就需要我们对synchronizedLock有个清晰的了解。


Locksynchronized

synchronized 是JAVA提供的强制原子性的内置锁机制。一个synchronized有两部分:锁对象的引用 (synchronized 方法的锁,就是该方法所在对象本身),以及这个锁保护的代码块。每个Java对象都可以作为一个用于同步的锁的角色,这些内置的锁被成为内部锁,线程进入 synchronized 块之前会自动获得锁,退出、报错异常、时会释放锁。内部锁是一种互斥锁,这就是说,至多只有一个线程可以获得锁,所以被 synchronized 声明的方法或代码块至多只有一个线程可以进入。从而保证了线程安全。 大致有如下用法:

1
2
3
4
5
6
7
8
9
//作用于方法
public synchronized void synchronizedMethod(){
//do something
}

// 作用与代码块
synchronized (syncObj){
//do something
}

Lock 接口定义了一些抽象了锁操作,与内部锁机制不同,它提供了更加灵活的, 无条件的可轮训的定时的可中断的锁获取操作。 Lock 的接口定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
以下为Lock接口定义的大致解释,建议自行阅读JDK源码学习
*/
public interface Lock {
// 获取锁,如果没有则会被阻塞
void lock();
// 获取锁,但是允许中断
void lockInterruptibly() throws InterruptedException;
// 尝试获取锁,如果此刻获取不到则返回false,否则返回true
boolean tryLock();
// 在一定时间内尝试获取锁
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
// 返回一个新的Condition类别的实例
Condition newCondition();
}

大致用法为:

1
2
3
4
5
6
7
8
Lock lock = ...;
...
lock.lock();
try {
// access the resource protected by this lock
} finally {
lock.unlock();
}

Lock 接口的定义中可以看出,Locksynchronized 的区别:

  1. Lock 需要自己释放获得的锁(遗忘这个会容易导致错误,且不易排查)
  2. Lock 支持可中断的锁获取操作
  3. Lock 支持可定时的和可轮训的锁请求(由 trylock 方法实现),与无条件的锁获取想比,它具有更完善的错误恢复机制。
  4. Lock 支持非结构块的锁,可以在不同的程序块中操作锁。

ReentranLock

另外,我们有比较大的机会接触到 Lock 接口的实现类 ReentranLockReentranLock 支持公平性锁和非公平性锁(默认),这个选择是通过构造函数传入一个 boolean 类型的参数决定的,其内部是通过继承了AbstractQueuedSynchronizer实现了一个FairSync和一个NonFairSync来实现的,这个可以在以后讲AQS的时候再深入讲解(大家也可以自行去阅读 java.util.concurrent 下的各个接口和类,相信会大有收获)。ReentranLock 也是一个标准的互斥锁:一次最多只有一个线程能够持有相同的ReentranLockReentranLock 提供了和synchronized 相同的互斥和内存可见性。所以通常会将 ReentranLocksynchronized 放在一起对比。 可能的面试问题场景如下:

面试官: 有哪几种实现同步的方式? 面试者: synchronizedReentranLock 面试官: 那synchronizedReentranLock 有什么区别 面试者: 。。。

其实 ReentranLocksynchronized 的区别和Locksynchronized 的区别是一致的,只不过ReentranLock 多了个公平队列的支持


ReentranLocksynchronized 的选择

ReentranLockLock 接口的实现,所以其具有 Lock接口的特性。一般来说,不要ReentranLocksynchronized 混合使用,容易造成混淆。并且我们更倾向于使用简洁的内部锁机制synchronized。只有在需要使用ReentranLock的时候才应该使用。即:

当你需要 可定时的,可寻轮的与可中断的锁获取操作,公平队列,或者非块结构的锁 这些特性的时候才应该使用 ReentranLock。 否则最好还是使用 synchronized