Java并发包下的锁(1)——Lock的体系结构

在JDK1.5之前,在协调共享对象的访问时可使用的机制只有synchronized和volatile,这就是我们所熟知的内置锁;在JDK1.5中,著名并发编程大师Doug Lea使用Java编写了一个并发编程框架(java.util.concurrent.* => JUC),提供了更多并发编程的高级手段。例如可以通过显示加锁以保证多线程编程的可靠性,这就是大名鼎鼎的 显示锁。本系列文章将介绍一下 java.util.concurrent.locks 下的模块及其实现原理。

内置锁和显示锁
  • 在JDK1.5之前,为了保证对共享变量的同步访问,需要对共享变量加锁,以避免出现线程安全问题,这时我们使用的是synchronized,可以对代码块和方法加锁,这就是我们所熟知的内置锁,也可以称为隐式锁。Java中的内置锁被称为对象监视器(monitor),每个继承自java.lang.Object的对象都有一个对象监视器。内置锁是JDK基于JVM的一个重要实现。如下代码:
//源码
public class Synchronized {

    public static void main(String[] args) {
        
        synchronized (Synchronized.class){

        }
        
        m();
    }
    public static synchronized void m(){

    }
}

//对应的Java字节码
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
  stack=2, locals=3, args_size=1
     0: ldc           #2                  // class Synchronized
     2: dup
     3: astore_1
     4: monitorenter             //插入同步代码块的开始位置
     5: aload_1
     6: monitorexit              //插入同步代码块的结束位置
     7: goto          15
    10: astore_2
    11: aload_1
    12: monitorexit              
    13: aload_2
    14: athrow
    15: invokestatic  #3                  // Method m:()V
    18: return

public static synchronized void m();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED //方法同步使用ACC_SYNCHRONIZED标记
Code:
  stack=0, locals=0, args_size=0
     0: return
  LineNumberTable:
    line 14: 0
}
  • JDK1.5之后新增的显示锁是一种更为高级的功能,作为内置锁不适用时的选择。Lock完全是由Java写成的,在Java这个层面无关于JVM实现。显示锁在JDK1.6之前效率远高于synchronized,即使JDK1.6对synchronized做了优化(偏向锁,轻量级锁等),其效率还是略高于synchronized。虽然它缺少了synchronized隐式获取锁和释放锁的便捷性,但是却拥有了锁获取与释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。
lock 的体系结构

下图展示的是 java.util.concurrent.locks 下几个重要的接口和类的继承关系:

Java并发包下的锁(1)——Lock的体系结构_第1张图片

简单介绍与分析如下:

  • AbstractOwnableSynchronizer 是一个抽象的同步器类(关于同步器后文会有详细介绍),此类是创建锁和相关同步器的基础,可以由线程以独占方式拥有同步器

  • AbstractQueuedLongSynchronizer 继承于 AbstractOwnableSynchronizer,是一个抽象的队列同步器,以long来维护同步状态的一个 AbstractQueuedSynchronizer 版本,此类的结构、属性和方法与 AbstractQueuedSynchronizer 相同,只是所有与状态相关的参数和结果都定义为long,当创建需要64位状态的多级别锁时将会用到该同步器

  • AbstractQueuedSynchronizer 继承于 AbstractOwnableSynchronizer,是一个以 int 值来维护同步状态的同步器。该同步器是用来构建锁或者其他同步组建的基础框架,它使用一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作

  • LockSupport 定义了一组公共的静态方法,是构建同步组建的基础工具。AbstractQueuedSynchronizer 和 AbstractQueuedLongSynchronizer 实现同步需要其支持

  • Condition 接口提供了类似于Object的监视器方法,可以与Lock配合实现等待/通知机制。

  • Lock 接口定义了锁获取与释放的基本操作

  • ReadWriteLock 接口定义了获取读锁和写锁的方法

  • ReentrantLock 实现了Lock接口,其操作都有委派给了内部类 Sync,Sync继承了同步器类 AbstractQueuedSynchronizer

  • ReentrantReadWriteLock 实现了 ReadWriteLock 接口,其操作都有委派给了内部类 Sync,Sync继承了同步器类 AbstractQueuedSynchronizer

Lock 接口简介

上面聊了一下Lock的体系结构,大体清楚了Java并发包中的锁是什么,现在就具体说说Lock接口给我们提供了哪些API,以及我们应该如何使用这些API

下面就是Lock接口中的API

public interface Lock {

    // 获取锁,调用该方法时当前线程将会获取锁,当获得锁之后将会从该方法返回
    void lock();

    // 可中断的获取锁,和lock()方法的不同之处在于该方法会响应中断,即在锁的获取中可以中断当前线程
    void lockInterruptibly() throws InterruptedException;

    // 尝试非阻塞的获取锁,调用该方法后立即返回,如果能够获取则返回true,否则返回false
    boolean tryLock();

    // 超时的获取锁,当线程出现以下3中情况时返回:
    // 1. 当前线程在超时时间内获得锁
    // 2. 当前线程在超时时间内被中断
    // 3. 超时时间结束,返回false
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    // 释放锁
    void unlock();

    // 获取等待通知的组建,该组建和当前的锁绑定,当前线程只有获得了锁
    // 才能调用该组件的wait()方法,调用后,当前线程将释放锁
    Condition newCondition();
}

Lock的简单用法如下

public class TestLock {

  private ReentrantLock lock = new ReentrantLock();
  
  public void test() {
      lock.lock();
      
      try {
          // 业务逻辑
      } finally {
          lock.unlock();
      }
  }
}

 ReentrantLock是可重入锁,实现了Lock接口,后续文章会提到。这里需要注意两点:

  1. 一定要在finally中释放锁,否则后续可能会出现很严重的问题。

  2. 不要将获取锁的过程写在try块中,因为假如在获取锁时发生了异常,异常抛出的同时也会导致锁无法释放

Lock接口具备而synchronized没有的特性如下:

特性 描述
尝试非阻塞的获取锁 当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁
能被中断的获取锁 获取锁的线程能够响应中断:当获取到锁的线程被中断时,中断异常将会抛出,同时锁被释放
超时获取锁 在指定的截止时间之前获取锁,如果到了截止时间仍无法获取锁,则返回
参考

《并发编程的艺术》

《并发编程实战》

你可能感兴趣的:(◆【编程语言】)