一、背景
阿里技术的公众发了一篇文章
《谁是代码界3%的王者?》,
提到“在Java代码界,有些陷阱外表看起来是个青铜实际上是王者,据说97%工程师会被“秒杀””
给出了5道题,非常考验基础。
本文简单解读第五题。
二、代码
给出的示例代码:
public class LockTest {
private final static Lock lock = new ReentrantLock();
public static void main(String[] args) {
try {
lock.tryLock();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

给出的问题:
下列哪种说法是错误的:
A: lock是非公平锁
B: finally代码块不会抛出异常
C: tryLock获取锁失败则直接往下执行
答案应该是B(不过问题和源码并不是很匹配)。
三、解读
我们改造一些上面的源代码,新增一个子线程,让子线程先获取锁,我们看会发生什么:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
private final static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
if (lock.tryLock()) {
System.out.println("子线程获取锁"+"成功");
try {
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("子线程释放锁");
lock.unlock();
}
}
});
thread.start();
TimeUnit.SECONDS.sleep(1);
try {
// 有可能没获取到锁
boolean tryLock = lock.tryLock();
System.out.println("主线程获取锁"+(tryLock?"成功":"失败"));
} catch (Exception e) {
e.printStackTrace();
} finally {
// 有可能没获取到锁调用了释放
System.out.println("主线程释放锁");
lock.unlock();
}
}
}
运行结果:
子线程获取锁成功
主线程获取锁失败
主线程释放锁
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
at com.chujianyun.common.style.LockTest.main(LockTest.java:41)
子线程释放锁
接下来我们分析原因。
先看A选项: lock是非公平锁,我们看到lock的类型为ReentrantLock。
还是老规矩,源代码代码:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
很明显默认的空参数构造方法创建的的确是非公平锁。
另外我们发现它还提供了构造方法支持指定是否为公平锁。
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁和非公平锁的主要区别:
更详细内容参见
《不可不说的Java“锁”事》再看C选项tryLock获取锁失败则直接往下执行。
继续看源码
java.util.concurrent.locks.ReentrantLock#tryLock()
/**
* Acquires the lock only if it is not held by another thread at the time
* of invocation.
*
* <p>Acquires the lock if it is not held by another thread and
* returns immediately with the value {@code true}, setting the
* lock hold count to one. Even when this lock has been set to use a
* fair ordering policy, a call to {@code tryLock()} <em>will</em>
* immediately acquire the lock if it is available, whether or not
* other threads are currently waiting for the lock.
* This "barging" behavior can be useful in certain
* circumstances, even though it breaks fairness. If you want to honor
* the fairness setting for this lock, then use
* {@link #tryLock(long, TimeUnit) tryLock(0, TimeUnit.SECONDS) }
* which is almost equivalent (it also detects interruption).
*
* <p>If the current thread already holds this lock then the hold
* count is incremented by one and the method returns {@code true}.
*
* <p>If the lock is held by another thread then this method will return
* immediately with the value {@code false}.
*
* @return {@code true} if the lock was free and was acquired by the
* current thread, or the lock was already held by the current
* thread; and {@code false} otherwise
*/
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
从源码里可以看出失败会返回false,而且没有抛异常。
另外看Lock接口的注释可以发现,原示例中的用法不规范:
java.util.concurrent.locks.Lock#tryLock()
上面的专门给出了锁的典型用法:
Lock lock = ...;
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}
从代码我们可以看出,我们通过lock.tryLock的返回值判断加锁是否成功,来分别执行成功和失败的逻辑。
并且加锁成功的逻辑中解锁的代码要放到finally代码块中保证异常仍然会释放锁。
另外从改造后的例子可以看出,lock.tryLock获取锁失败会继续执行。
我们再看B选项: finally代码块不会抛出异常
根据我们改造的源码可以看到,finally代码块可能抛出IllegalMonitorStateException。
同样的我们看源码:
java.util.concurrent.locks.ReentrantLock#unlock
/**
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented. If the hold count is now zero then the lock
* is released. If the current thread is not the holder of this
* lock then {@link IllegalMonitorStateException} is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
* hold this lock
*/
public void unlock() {
sync.release(1);
}
这里写的很清楚,如果当前线程并不持有此锁时,调用unlock会报IllegalMonitorStateException。
四、拓展
4.1 正确用法
我们去java.util.concurrent.locks.ReentrantLock的源码顶部看注释发现,官方给出了一个标准用法范例:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // 直到条件满足,否则一直阻塞
try {
// ... 函数体
} finally {
lock.unlock()
}
}
}}
为什么不把lock.lock()放在try里呢??
因为如果放到try内,且前面有其他代码出现异常,并没有获取到锁,则finnaly解锁会抛异常。
4.2 lock.lock()获取失败后面会不会执行?
java.util.concurrent.locks.ReentrantLock#lock
/**
* Acquires the lock.
*
* <p>Acquires the lock if it is not held by another thread and returns
* immediately, setting the lock hold count to one.
*
* <p>If the current thread already holds the lock then the hold
* count is incremented by one and the method returns immediately.
*
* <p>If the lock is held by another thread then the
* current thread becomes disabled for thread scheduling
* purposes and lies dormant until the lock has been acquired,
* at which time the lock hold count is set to one.
*/
public void lock() {
sync.lock();
}
通过注释就可以看到如果锁被其他线程所持有,则当前线程被阻塞直到获取到锁,此时锁的持有数量也会被设置为1。
五、总结
可以说JDK源码是我们学习JDK类的一个最关键且最好的素材。
我们要学习研究某个类或者方法的时候,超级!超级!超级!建议先进源码去看注释。
我们学习的时候,开发的时候有困惑的地方尽量都要先去看源码,不要等面试的时候再去”突击“!!
创作不易,如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。