Java面试题之synchronized平台级锁和Lock实现的锁区别

简介: 目录一、Lock类层次结构及相关API1、Lock类层级结构2、Lock接口相关API3、关于Condition二、synchronized VS Lock1、synchronized实现的锁优缺点2、Lock实现的锁优缺点三、手撸一把简单的ReentrantLock1、ReentrantLock实现简单流程2、代码示例3、测试用例

目录

一、Lock类层次结构及相关API

1、Lock类层级结构

2、Lock接口相关API

3、关于Condition

二、synchronized VS Lock

1、synchronized实现的锁优缺点

2、Lock实现的锁优缺点

三、手撸一把简单的ReentrantLock

1、ReentrantLock实现简单流程

2、代码示例

3、测试用例

一、Lock类层次结构及相关API



1、Lock类层级结构

ReentrantLock和ReentrantReadWriteLock都是java.util.concurrent并发包下的工具类,ReentrantLock实现了Lock接口,ReentrantReadWriteLock实现了ReadWriteLock接口,而其中的ReadLock和WriteLock又实现了Lock接口。

7bf56c62e1584568abbd652fdb8253b1.png

2、Lock接口相关API

为了区别synchronized和ReentrantLock,我们先了解一下Lock接口相关的API。d22d48c26d754fd5a90b40f8d898487f.png


结论:

1、lock()最常用。

2、lockInterruptibly()方法一般更昂贵,有的impl可能没有实现lockInterruptibly,只有真的需要响应中断时才使用。

3、关于Condition

Object中的wait()、notify()、notifyAll()只能和synchronized关键字配合使用,可以唤醒一个或全部线程。Condition需要与Lock配合使用,提供多个等待集合,更精确的控制。


备注:如果说Lock代替了同步代码块或同步方法的加解锁逻辑,那么Condition则是代替了Object的等待和唤醒逻辑。

二、synchronized VS Lock



1、synchronized实现的锁优缺点

我们先说说synchronized实现的平台级锁的优点:


使用简单,语义清晰,在方法上加上synchronized关键字或者使用同步代码块即可。

由JVM提供,提供了多种优化方案,如锁粗化、锁消除、偏向锁、轻量级锁、重量级锁等,关于这些优化特性请参考:Java面试题之synchronized关键字原理以及锁相关。

锁的释放由JVM完成,不用人工干预,也降低了死锁的可能性。

再说说它的缺点:

无法实现一些锁的高级功能,如超时锁、中断锁、读写锁、共享锁、公平锁。


2、Lock实现的锁优缺点

Lock实现的锁主要弥补了synchronized的缺点,比如上面提到的锁的高级功能,如超时锁、中断锁、读写锁、共享锁、公平锁这些。


再说一下它的缺点:


需手动释放锁,新手使用不当可能造成死锁。

没有synchronized实现的锁那么多优化项。

三、手撸一把简单的ReentrantLock


1、ReentrantLock实现简单流程

先介绍一下一些关键属性,如下:

  • waiters代表锁池,说白了就是抢锁失败线程的等待队列。
  • owner代表成功获取到锁的线程。
  • count用来标记锁的可重入次数。93497eb120bc43b1adb1bba5c32044dc.png
  • 先描述下加锁流程:


如果可重入次数为0代表锁还没有被任何线程持有,这时可以通过CAS(0, count + 1)操作进行抢锁。

如果可重入次数不为0,则判断当前抢锁的线程是不是持有锁的线程,如果是则将可重入次数+1即可。

如果如上两种条件都不满足,则直接算抢锁失败。

抢锁失败的线程直接进入等待队列,并阻塞等待。

再描述下解锁流程:


如果调用解锁方法的线程不是持有锁的线程,则抛出IllegalMonitorStateException,否则第2步。

将可重入次数-1,如果可重入次数为0则代表解锁成功。

解锁成功后唤醒队列头部等待的抢锁线程。

2、代码示例

public class NicksReentrantLock implements Lock {
  // 用来标识哪个线程获取到锁
  private Thread owner;
  // 重入次数
  private AtomicInteger counter = new AtomicInteger(0);
  // 等待队列
  private BlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
  @Override
  public void lock() {
    if (!tryLock()) {
      waiters.offer(Thread.currentThread());
      // 循环解决伪唤醒问题
      while (true) {
        // 如果队列头部是当前线程说明可以抢锁
        if (Thread.currentThread() == waiters.peek()) {
          // 若抢锁成功则出队列
          if (tryLock()) {
            waiters.poll();
            return;
          }
        }
        // 若当前线程不在队列头部或者抢锁失败则挂起
        LockSupport.park();
      }
    }
  }
  @Override
  public void lockInterruptibly() throws InterruptedException {
  }
  @Override
  public boolean tryLock() {
    int ct = counter.get();
    if (ct == 0) {
      // 重入次数为0说明当前线程可以通过CAS操作获取锁
      if (counter.compareAndSet(0, ct + 1)) {
        owner = Thread.currentThread();
        return true;
      }
      return false;
    }
    // 不为0则判断获取锁的线程是否是当前线程,如果是当前线程,则将可重入次数加1
    if (owner == Thread.currentThread()) {
      counter.set(ct + 1);
      return true;
    }
    return false;
  }
  @Override
  public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
    return false;
  }
  @Override
  public void unlock() {
    // 解锁成功应该唤醒队列头部的线程
    if (tryUnlock()) {
      Optional.ofNullable(waiters.peek()).ifPresent(LockSupport::unpark);
    }
  }
  public boolean tryUnlock() {
    // 如果当前解锁的线程不是获取到锁的线程则抛异常
    if (Thread.currentThread() != owner) {
      throw new IllegalMonitorStateException();
    }
    int ct = counter.get();
    int nextCt = ct - 1;
    counter.set(nextCt);
    if (nextCt == 0) {
      owner = null;
      return true;
    }
    return false;
  }
  @Override
  public Condition newCondition() {
    return null;
  }
}


3、测试用例

public class Test {
  private static int count = 0;
  public static void main(String[] args) {
    NicksReentrantLock lock = new NicksReentrantLock();
    for (int index = 0; index < 10000; index++) {
      new Thread(() -> {
        try {
          lock.lock();
          count++;
        } finally {
          lock.unlock();
        }
      }).start();
    }
    LockSupport.parkNanos(1000 * 1000 * 1000);
    System.out.println("累加后的值为:" + count);
  }
}

备注:控制台输出为: 累加后的值为:10000

相关文章
|
24天前
|
Java 程序员 调度
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
55 9
|
29天前
|
安全 Java 程序员
Java面试必问!run() 和 start() 方法到底有啥区别?
在多线程编程中,run和 start方法常常让开发者感到困惑。为什么调用 start 才能启动线程,而直接调用 run只是普通方法调用?这篇文章将通过一个简单的例子,详细解析这两者的区别,帮助你在面试中脱颖而出,理解多线程背后的机制和原理。
60 12
|
2月前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
2月前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
77 3
|
2月前
|
Java
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
今日分享的主题是如何区分&和&&的区别,提高自身面试的能力。主要分为以下四部分。 1、自我面试经历 2、&amp和&amp&amp的不同之处 3、&对&&的不同用回答逻辑解释 4、彩蛋
|
2月前
|
Java
java中面向过程和面向对象区别?
java中面向过程和面向对象区别?
37 1
|
3月前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
107 14
|
3月前
|
存储 缓存 安全
java 中操作字符串都有哪些类,它们之间有什么区别
Java中操作字符串的类主要有String、StringBuilder和StringBuffer。String是不可变的,每次操作都会生成新对象;StringBuilder和StringBuffer都是可变的,但StringBuilder是非线程安全的,而StringBuffer是线程安全的,因此性能略低。
104 8
|
7月前
|
存储 缓存 Java
Java面试题:解释Java中的内存屏障的作用,解释Java中的线程局部变量(ThreadLocal)的作用和使用场景,解释Java中的锁优化,并讨论乐观锁和悲观锁的区别
Java面试题:解释Java中的内存屏障的作用,解释Java中的线程局部变量(ThreadLocal)的作用和使用场景,解释Java中的锁优化,并讨论乐观锁和悲观锁的区别
73 0
|
8月前
|
安全 Java 程序员
Java并发编程中的锁机制与优化策略
【6月更文挑战第17天】在Java并发编程的世界中,锁是维护数据一致性和线程安全的关键。本文将深入探讨Java中的锁机制,包括内置锁、显式锁以及读写锁的原理和使用场景。我们将通过实际案例分析锁的优化策略,如减少锁粒度、使用并发容器以及避免死锁的技巧,旨在帮助开发者提升多线程程序的性能和可靠性。

热门文章

最新文章