ReentrantLock那些事

前言

在说ReentrantLock之前,我们先说说并发吧。

在JDK1.5之前,并发处理常用的关键字synchronized。使用synchronized关键字,锁的获取和释放是隐式的,synchronized主要通过系统的monitorenter指令实现的。

那时候synchronized可以称为重量级锁,执行效率不是很高。

而Doug Lea编写的util.concurrent 包被纳入JSR-166标准。这里面就包含了ReentrantLock。

ReentrantLock为编写并发提供了更多选择。

使用

ReentrantLock的通常用法如下:

 public class X {
    private final ReentrantLock lock = new ReentrantLock();

    public void m() {
      lock.lock();  
      try {
        //TODO
      } finally {
        lock.unlock();
      }
    }
  }

原理

ReentrantLock主要是通过AbstractQueuedSynchronizer实现的,是一个重入锁,即一个线程加锁后仍然可以获得锁,不会出现自己阻塞自己的情况。

UML图

我们看一下它们的UML图。

这里写图片描述

可以看到ReentrantLock实现了Lock接口。

锁类型

ReentrantLock的两种锁类型,公平锁和非公平锁。

这里写图片描述

这里写图片描述

源码分析

我们先来看下ReentrantLock的构造方法。

    public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

可以看到默认无参构造方法为非公平锁实现。如果想定义公平锁实现,可以传入true来控制。

它的lock方法:

    public void lock() {
        sync.acquire(1);
    }
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

公平锁和非公平锁各有自己的实现方式。我们来看下他们的tryAcquire方法。

非公平锁源码:

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    final boolean nonfairTryAcquire(int acquires) {         
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

可以看到,非公平锁首先判断AQS(AbstractQueuedSynchronizer)中的state是否为0,0表示没有线程持有该锁,当前线程就尝试获取锁。

如果不是0,那在判断是不是当前线程持有该锁,如果是,就会增加state,改变state状态。(因此ReentranLock支持重入)。

公平锁源码:

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        @ReservedStackAccess
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                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 final boolean hasQueuedPredecessors() {
        Node t = tail; 
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

公平锁的tryAcquire方法,可以看到,相比非公平锁,多了hasQueuedPredecessors方法,这个方法是判断队列中是否有其他线程,如果没有,线程才会尝试获取锁,如果有,会先把锁分配给队列的线程,因此称为公平锁。

这儿可以看到,非公平锁的效率比公平锁要高。

这是tryAcquire方法,如果尝试获取锁失败了呢?

那就会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法啦。

我们先来看一下addWaiter方法。

    private Node addWaiter(Node mode) {
        Node node = new Node(mode);

        for (;;) {
            Node oldTail = tail;
            if (oldTail != null) {
                node.setPrevRelaxed(oldTail);
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return node;
                }
            } else {
                initializeSyncQueue();
            }
        }
    }

可以看到,这个方法会把线程添加到队列尾,同时,for(;;)循环保证添加成功,直到return出去。

添加后,调用acquireQueued方法,这个方法为挂起等待线程。

看下该方法源码:

    final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

可以看到,如果节点为头节点,就尝试获取一次锁,如果成功,就返回。

否则判断该线程是否需要挂起,如果需要的化就调用parkAndCheckInterrupt挂起。

调用LockSupport.park方法挂起线程,直到被唤醒。

selfInterrupt方法:

    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();
        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

调用interrupt方法,中断正在执行的线程(如果不是当前线程的话)。

释放锁unlock方法:

公平锁和非公平锁释放锁的方法是一样的。

    public void unlock() {
        sync.release(1);
    }
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

可以看到首先会判断当前线程是否是获得锁的线程,如果是重入锁需要将state减完才算是完全释放锁。

释放后调用unparkSuccessor唤起挂起线程。

总结

  1. 非公平锁的效率是比公平锁要高的。
  2. ReentranLock支持重入,因为增加了对自身线程的处理,通过state可以控制。
  3. 解锁操作应放到finally块里,避免使用锁时出现资源无法释放的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值