一、背景
阿里技术的公众发了一篇文章《谁是代码界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. * * 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()} will * 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). * * If the current thread already holds this lock then the hold * count is incremented by one and the method returns {@code true}. * * 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. * * 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.
*
*
Acquires the lock if it is not held by another thread and returns
* immediately, setting the lock hold count to one.
*
*
If the current thread already holds the lock then the hold
* count is incremented by one and the method returns immediately.
*
*
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类的一个最关键且最好的素材。
我们要学习研究某个类或者方法的时候,超级!超级!超级!建议先进源码去看注释。
我们学习的时候,开发的时候有困惑的地方尽量都要先去看源码,不要等面试的时候再去”突击“!!
创作不易,如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。
————————————————
版权声明:本文为CSDN博主「明明如月学长」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/w605283073/article/details/92853707