Java并发编程之锁机制之Lock接口

该文章属于《Java并发编程》系列文章,若是想了解更多,请点击《Java并发编程之总目录》编程

前言

在上篇文章《Java并发编程之锁机制之引导篇》及相关实现类,咱们大体了解了Lock接口(以及相关实现类)在并发编程重要做用。接下来咱们就来具体了解Lock接口中声明的方法以及使用优点。bash

Lock简介

Lock 接口实现类提供了比使用 synchronized 方法和语句可得到的更普遍的锁定操做。此实现容许更灵活的结构,能够具备差异很大的属性,能够支持多个相关的 Condition (Condition实现类ConditonObject来实现线程的通知/与唤醒机制,关于Condition后期会进行介绍)对象。多线程

锁是用于控制多线程访问共享资源的工具。一般,锁提供对共享资源的独占访问:一次只有一个线程能够获取锁,对共享资源的全部访问都须要首先获取锁。可是,一些锁能够容许同时访问共享资源,例如ReadWriteLock并发

虽然使用关键字synchronized修饰的方法或代码块,会使得在监视器模式(ObjectMonitor)下编程变得很是容易(经过synchronized块或者方法所提供的隐式获取释放锁的便捷性)。虽然这种方式简化了锁的管理,可是某些状况下,仍是建议采用Lock接口(及其相关子类)提供的显示的锁的获取和释放。例如,针对一个场景,手把手进行锁获取和释放,先得到锁A,而后再获取锁B,当锁B得到后,释放锁A同时获取锁C,当锁C得到后,再释放B同时获取锁D,以此类推。这种场景下, synchronized关键字就不那么容易实现了,而Lock接口的实现类容许锁在不一样的做用范围内获取和释放,并容许以任何顺序获取和释放多个锁。ide

Lock接口中的方法

关于Lock接口中涉及到的方法具体以下:(建议直接在PC端查看,手机上有可能看的不是很清楚) 工具

lock_method.png
从上表中,咱们就能够得出使用Lock接口实现的锁机制与使用传统的synchronized的区别

  1. 尝试非阻塞地获取锁:当线程尝试获取锁,若是这一时刻锁没有被其余线程获取到,则成功获取并持有锁。
  2. 能被中断的获取锁:与synchronized不一样,获取到锁的线程可以响应中断,当获取到锁的线程被中断时,中断异常会被抛出,同时锁也会被释放。
  3. 超时获取锁:在指定的截止时间以前获取锁,若是截止时间到了任然没法获取到锁,则返回。

Lock简单使用与注意事项

其中Lock的使用方式也很简单,具体代码以下所示:post

Lock lock = ....;具体实现类
lock.lock();
try {
} finally {
lock.unlock();//建议在finally中释放锁
}
复制代码

当锁定和解锁发生在不一样的范围时,必定要注意确保在持有锁时执行的全部代码都受到try-finally或try-catch的保护,以确保在必要时释放锁。不要将获取锁的过程写在try块中,由于若是在获取锁(自定义锁的实现)时发生了异常,异常抛出的同时,也会致使锁无端释放(由于一旦发生异常,就会走finally语句,若是这个异常(多是用户自定义异常,用户能够本身处理)须要线程1来处理,可是接着执行了lock.unlock()语句致使了锁的释放。那么其余线程就能够操做共享资源。有可能破坏程序的执行结果)。ui

Lock相关实现类实现锁机制

为了使用Lock接口实现相关锁功能时,会涉及如下类和接口,这里仍是把上篇文章提到的UML图展现出来:spa

lock.png

上图中,线程

  1. 绿色部分为:其中ReentrantLock(重入锁)、WriteLock、ReadLock都是Lock的实现类。Segment为ReentrantLock的子类(在后续文章,ConcurrentHashMap的讲解中咱们会说起)。 ReentrantReadWriteLock (读写锁)的实现使用了WriteLock与ReadLock类。
  2. 紫色部分为:其中AbstractQueuedSynchronizerAbstractQueuedLongSynchronizer都为AbstractOwnableSynchronizer的子类,该两个类中都维护了一个同步队列,用于线程的并发执行。在该两个类中拥有名为ConditionObject(为Conditon的实现类)的内部类,只是其内部实现不一样。在ConditionObject内部维护了一个等待队列,用于控制线程的等待与唤醒。

基本代码结构

在了解了Lock相关实现类实现锁机制后,这里给实现该锁机制的大体代码结构(根据不一样需求,部分方法实现可能不同,这里只是一个参考,并非样本代码)。具体代码以下所示:

class LockImpl implements Lock {

    private final sync mSync = new sync();
    @Override
    public void lock() {
        mSync.acquire(1);
    }
    @Override
    public void lockInterruptibly() throws InterruptedException {
        mSync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return mSync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return mSync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        mSync.release(1);
    }

    @Override
    public Condition newCondition() {
        return mSync.newCondition();
    }
    
	 //这里也能够继承AbstractQueuedLongSynchronizer
    private static class sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean isHeldExclusively() {...}
        @Override
        protected boolean tryAcquire(int arg) {...}
        @Override
        protected boolean tryRelease(int arg) {...}
        @Override
        protected int tryAcquireShared(int arg) {...}
        @Override
        protected boolean tryReleaseShared(int arg) {...}
        final ConditionObject newCondition() {...}
    }
}
复制代码

从代码中咱们能够看出,在整个Lock接口下实现的锁机制中,AQS(这里咱们将AbstractQueuedSynchronizer 或AbstractQueuedLongSynchronizer统称为AQS)是实现锁的关键,整个锁的实现是在Lock类的实现类中聚合AQS来实现的,从代码层面上来讲,Lock接口(及其实现类)是面向使用者的,它定义了使用者与锁交互的接口(好比能够容许两个线程并行访问),隐藏了实现细节。AQS与Condition才是真正的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操做。

总结

  1. Lock接口(及其实现类)相比synchronized有以下优势:
  • 锁的释放与获取不在是隐式的,容许锁在不一样的做用范围内获取和释放`,并容许以任何顺序获取和释放多个锁。
  • 能被中断的获取锁,获取到锁的线程可以响应中断,当获取到锁的线程被中断时,中断异常会被抛出,同时锁也会被释放
  • 超时获取锁:在指定的截止时间以前获取锁,若是截止时间到了任然没法获取到锁,则返回。
  1. 在使用Lock的时候注意,必定要确保必要时释放锁
  2. 在整个Lock接口下实现的锁机制中,AQS(上文进行了统称)与Condition才是真正的实现者。
相关文章
相关标签/搜索