Java中的Lock与ReentrantLock

文章目录

  • Lock
  • 可重入锁ReentrantLock
    • 可重入分析
    • 公平锁与非公平锁分析
    • 自己实现一个可重入锁

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时,线程完全释放同步资源。

  1. 获得锁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;
}
  1. 释放锁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接口方法的实现
}

你可能感兴趣的:(多线程)