lock.lock() 写在 try 代码块内部行吗?

熟悉 Java 并发的朋友,想必都会对 Lock 接口很熟悉,它是从 JDK1.5 以后提供给开发者的另一种线程同步的方式。下面是使用它的一个具体实现类:ReentrantLock 进行加锁解锁的一个小例子:

 Lock lock = new ReentrantLock();
 lock.lock();
 try {
   // access the resource protected by this lock
 } finally {
   lock.unlock();
 }

上述这个段代码,其实也是下面链接中, JDK 的 document 举的使用示例。

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html

我们注意到,lock.unlock();是被放入 finally 代码块里的,这是为了保证出现异常时,锁依然能被释放掉,避免死锁的产生。

我们还要注意到,加锁的过程:lock.lock();,并没有放在 try 代码块内,而且你会发现,JDK 文档中很多使用 lock 的地方都是将加锁过程:lock.lock();放在了 try 的外部。

但是今天发现网上有一些文章,给出的代码示例中,lock.lock();被放在了 try 代码块的内部,如下所示:

 Lock lock = new ReentrantLock();
 try {
   lock.lock();
   // access the resource protected by this lock
 } finally {
   lock.unlock();
 }

这样做合理吗?首先给出答案,千万不要这样做。。。

为什么呢?假设现在我们使用上述的实现,将lock.lock();放在了 try 的内部。

考虑一种情况,如果说在获取锁时发生了异常,那么肯定也会走 finally 代码块,执行lock.unlock();去释放锁,可问题是我还没获取到锁啊!!!

那么会发生什么呢?我们从源码的角度来分析,首先来看 ReentrantLock 的 unlock() 方法。

    public void unlock() {
        sync.release(1);
    }

sync 是继承自抽象类 AbstractQueuedSynchronizer 的一个自定义同步器框架,AbstractQueuedSynchronizer 呢,也就是俗称的 AQS,它是 J.U.C 包的核心。

unlock() 首先会调用 sync 的 release() 方法,我们接着往下看这个方法。

   public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

release() 这个方法呢,其实是由 AQS 实现的,被 final 修饰,不允许被重写,它实际上会先去调用 tryRelease() 方法,而 tryRelease() 这个方法就是自定义同步框架时必须要重写的方法之一了,我们可以先看一下 AQS 中的 tryRelease() 方法源码。

    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

被 protected 修饰,很明显就是让子类去 Override 的,而且方法体内只有一句抛出异常的代码,所以这个 tryRelease() 方法是必须重写的。我们接下来来看看 ReentrantLock 的重写后的 tryRelease() 方法。这里为了方便大家抓住重点,我直接截图不贴代码了。
lock.lock() 写在 try 代码块内部行吗?_第1张图片
注意我圈红的这段代码,tryRelease() 方法会判断当前线程是否是持有锁的线程!如果不是,那么它会抛出 IllegalMonitorStateException!

好了,我们可以回到正题了,现在我们明白了,在 try-finally 外加锁的话,如果因为发生异常导致加锁失败,try-finally 块中的代码不会执行。相反,如果在 try{ } 代码块中加锁失败,finally 中的代码无论如何都会执行,但是由于当前线程加锁失败并没有持有 lock 对象锁,所以程序会抛出异常。

你可能感兴趣的:(Java并发)