谁是代码界3%的王者?- 第五题Lock的简单解读

简介: 谁是代码界3%的王者?- 第五题Lock的简单解读

一、背景

阿里技术的公众发了一篇文章《谁是代码界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


相关文章
|
5月前
|
Java 开发者
奇迹时刻!探索 Java 多线程的奇幻之旅:Thread 类和 Runnable 接口的惊人对决
【8月更文挑战第13天】Java的多线程特性能显著提升程序性能与响应性。本文通过示例代码详细解析了两种核心实现方式:Thread类与Runnable接口。Thread类适用于简单场景,直接定义线程行为;Runnable接口则更适合复杂的项目结构,尤其在需要继承其他类时,能保持代码的清晰与模块化。理解两者差异有助于开发者在实际应用中做出合理选择,构建高效稳定的多线程程序。
68 7
|
6月前
|
设计模式 数据库 Python
震撼!Python元类:解锁编程宇宙的终极秘密武器
【7月更文挑战第4天】Python的元类是创建类的类,用于定制类的行为。例如,通过元类`MyMeta`,可在创建类时动态添加属性,如`version`。在ORM中,元类能自动将类属性映射为数据库表字段。另外,元类也能实现设计模式,如单例模式,确保类只有一个实例。元类提供对Python底层机制的控制,增强了代码的灵活性和功能性。
35 0
|
4月前
|
Java
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
47 7
|
5月前
|
Java
多线程同步新姿势:Lock接口助你“一统江湖”!
多线程同步新姿势:Lock接口助你“一统江湖”!
54 2
|
5月前
|
程序员 调度 C++
解锁Ruby并发编程新境界!Fiber与线程:轻量级VS重量级,你选哪一派引领未来?
【8月更文挑战第31天】Ruby提供了多种并发编程方案,其中Fiber与线程是关键机制。Fiber是自1.9版起引入的轻量级并发模型,无需独立堆栈和上下文切换,由程序员控制调度。线程则为操作系统级别,具备独立堆栈和上下文,能利用多核处理器并行执行。通过示例代码展示了Fiber和线程的应用场景,如任务调度和多URL数据下载,帮助开发者根据需求选择合适的并发模型,提升程序性能与响应速度。
68 0
|
5月前
|
安全 Java 调度
震撼揭秘!手撕并发编程迷雾,Semaphore与CountDownLatch携手AQS共享模式,让你秒懂并发神器背后的惊天秘密!
【8月更文挑战第4天】在Java并发编程中,AbstractQueuedSynchronizer (AQS) 是核心框架,支持独占锁与共享锁的实现。本文以Semaphore与CountDownLatch为例,深入解析AQS共享模式的工作原理。Semaphore通过AQS管理许可数量,控制资源的并发访问;而CountDownLatch则利用共享计数器实现线程间同步。两者均依赖AQS提供的tryAcquireShared和tryReleaseShared方法进行状态管理和线程调度,展示了AQS的强大功能和灵活性。
46 0
|
8月前
|
算法 Java 程序员
论文翻译 | 【深入挖掘Java技术】「底层原理专题」深入分析一下并发编程之父Doug Lea的纽约州立大学的ForkJoin框架的本质和原理
本文深入探讨了一个Java框架的设计、实现及其性能。该框架遵循并行编程的理念,通过递归方式将问题分解为多个子任务,并利用工作窃取技术进行并行处理。所有子任务完成后,其结果被整合以形成完整的并行程序。 在总体设计上,该框架借鉴了Cilk工作窃取框架的核心理念。其核心技术主要聚焦于高效的任务队列构建和管理,以及工作线程的管理。经过实际性能测试,我们发现大多数程序的并行加速效果显著,但仍有优化空间,未来可能需要进一步研究改进方案。
92 3
论文翻译 | 【深入挖掘Java技术】「底层原理专题」深入分析一下并发编程之父Doug Lea的纽约州立大学的ForkJoin框架的本质和原理
|
8月前
|
监控 安全 Java
CompletableFuture探秘:解锁Java并发编程的新境界
CompletableFuture探秘:解锁Java并发编程的新境界
252 0
|
C语言
C语言练级之路(num6)10个基础小题目等你来挑战
首先今天有点迟了,原因就是去看了一下别的大佬的有关十六进制转八进制的博客,看的我现在人都有点不正常,要不是我目前还不会写,我觉得有的博主的写法我属实不能理解,等我哪天研究会了,……

热门文章

最新文章