Lock
Lock是JDK1.5之后提供的,它是Java语法级别的锁。加锁使用lock.lock(),解锁使用lock.unlock()。需要注意的是,它的unlock,必须放在finally中进行
,因为如果在加锁后,代码出现了异常,是不会释放锁的。
与synchronized对比:
- synchronized的加锁和解锁都由JVM实现,出现异常会自动释放锁,Lock需要手动释放锁,并且必须在finally中释放。
- Lock可以实现中断锁,在获得锁的线程被中断时,中断异常会被抛出,同时锁会被释放。
- Lock可以使用非阻塞方式,尝试获得锁,如果没有获得,直接返回。synchronized拿不到锁会阻塞在那里。
- Lock可以使用超时获得锁,在指定时间内没有获得锁,就直接返回。
Lock的底层是AbstractQueuedSynchronizer实现的。
可重入锁ReentrantLock
可重入分析
synchronized是隐形的可重入的,当前获得锁的线程,可以直接再次获得锁。
ReentrantLock利用AQS实现可重入效果分析:
每次线程获得同步资源时,state + 1,每次线程释放同步资源时,state -1 。当state为0时,线程完全释放同步资源。
获得锁nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 同步资源是空闲的
if (c == 0) {
// 同步状态为0,使用CAS获取同步资源,获取成功设置当前线程为同步资源持有者
// 获取失败直接返回
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 同步资源已经被当前线程获取,再次进入时,state+1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
释放锁tryRelease()
protected final boolean tryRelease(int releases) {
// 每次释放锁,state减1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// state为0表示当前线程已经完全释放了,清空持有同步资源的线程标识
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
公平锁与非公平锁分析
公平锁意味着在锁释放时,等待时间更长的线程会优先获得锁。在ReentrantLock的实现中,在线程尝试获得锁时,判断同步队列中是否有前驱节点,有的话就无法获得锁。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// hasQueuedPredecessors是判断同步队列中的第一个等待线程是否是当前线程,是的话就返回false,否的话返回true
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
但是非公平锁的效率比公平锁高,因为公平锁的实现上,在当前线程被唤醒后尝试拿锁的时候,如果等待在第一个的线程不是当前线程,那么就浪费掉了这次拿锁的机会,线程重新进入阻塞状态。
自己实现一个可重入锁
public class MyReentrantLock implements Lock {
private static final Sync sync = new Sync();
private final static class Sync extends AbstractQueuedSynchronizer {
// 获取重入锁
@Override
protected boolean tryAcquire(int arg) {
// 拿到当前同步状态
int state = getState();
// 如果同步状态是空闲的,直接让当前线程获取锁
if (state == 0) {
if (compareAndSetState(0,arg)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
} else {
// 当前同步状态被占用,判断是否是当前线程获取的
if (getExclusiveOwnerThread() == Thread.currentThread()) {
// 是的话把状态更新为 state + arg(当前线程持有锁,无需同步)
setState(state + arg);
return true;
}
}
return false;
}
// 重入锁的释放
@Override
protected boolean tryRelease(int arg) {
int newState = getState() - arg;
if (newState < 0) {
throw new IllegalMonitorStateException();
}
// 持有锁的线程完全释放锁
if (newState == 0) {
setState(0);
setExclusiveOwnerThread(null);
return true;
}
// 没有完全释放锁,所以不能返回true
setState(newState);
return false;
}
}
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void unlock() {
sync.release(1);
}
... // 其他Lock接口方法的实现
}