Java 并发之Lock接口

何谓Lock

它是java.util.concurrent.locks包下的一个类

老样子,来通过官方文档来详细了解Lock是什么以及怎么用

image.png

  • Lock提供了比使用synchronized方法和语句块更广泛的锁操作。就是加锁的方式更灵活。它们允许更灵活的结构,可能具有完全不同的属性,并且可能支持多个关联的Condition对象。

  • 通常,lock提供对共享资源的独占访问:一次只有一个线程可以获取lock,所有对共享资源的访问都需要先获取lock但是某些lock可能允许并行访问共享资源,例如ReadWriteLockReadLock

    • 关于并行访问共享资源,例如,在多线程情况下,有些线程是读操作,有些线程是写操作,对于读操作的共享资源其实可以并行的访问它,而不需要限制一次一个线程

  • synchronized方法或语句的使用提供对与每个对象关联的隐式monitor lock的访问,但强制所有锁获取和释放以块结构方式发生:当获取多个锁时(一个线程获取多个对象的锁),它们必须以相反的顺序释放,并且所有锁都必须在它们被获取的同一个词法范围内(语句块)释放。

image.png

  • synchronized很方便,但有弊端:锁的获取和释放有顺序且都是自动的且要在同一个作用域内。

  • Lock接口的实现允许在不同范围(作用域)内获取和释放锁,并允许以任何顺序获取和释放多个锁,即消除了synchronized锁的自动释放,变成了手动。

  • 当锁定和解锁发生在不同的作用域时,必须注意确保持有锁时执行的所有代码都受到 try-finallytry-catch 的保护,以确保在必要时释放锁

  • Lock实现通过提供非阻塞的获取锁尝试( tryLock() )、尝试获取可以被中断的锁(lockInterruptibly ,以及尝试获取锁tryLock()来提供比使用synchronized方法和语句更多的功能可以超时获取锁( tryLock(long, TimeUnit) ,即超过规定时间没有获取到锁就继续往下执行,而不会被阻塞)。而synchronized是会导致线程阻塞的。

image.png

  • Lock类还可以提供与隐式监视器(monitor,synchronized关键关键字其实是让线程获得监视器,只是在代码层看不见而已)锁完全不同的行为和语义,例如保证排序、不可重入使用或死锁检测。 如果实现提供了这样的专门语义,那么实现必须记录这些语义。

  • 请注意, Lock实例只是普通对象,它们本身可以用作synchronized语句中的目标。 获取Lock实例的监视器锁与调用该实例的任何lock方法没有特定的关系建议不要以这种方式使用Lock实例以避免混淆,除非在它们自己的实现中。

  • 除非另有说明,否则为任何参数传递null值都将导致抛出NullPointerException

  • 所有Lock实现都必须强制执行与内置监视器锁提供的相同内存同步语义

  • 锁获取的三种形式(可中断、不可中断和定时)可能在它们的性能特征、排序保证或其他实现质量方面有所不同。 此外,在给定的Lock类中可能无法中断正在进行的锁获取。 因此,实现不需要为所有三种形式的锁获取定义完全相同的保证或语义,也不需要支持正在进行的锁获取的中断。 需要一个实现来清楚地记录每个锁定方法提供的语义和保证。 它还必须遵守此接口中定义的中断语义,以支持锁定获取的中断:完全或仅在方法入口上。

  • 由于中断通常意味着取消,并且中断检查通常很少,因此实现可以倾向于响应中断而不是正常的方法返回。 即使可以证明中断发生在另一个动作可能已经解除了线程的阻塞之后也是如此。 实现应记录此行为。

接接着瞧一瞧该Lock接口中的方法

void lock();

image.png

  • 获得锁。
  • 如果锁不可用,则当前线程将被禁用以用于线程调度目的并处于休眠状态,直到获得锁
  • 实现时注意事项: Lock实现可能能够检测到对锁的错误使用,例如会导致死锁的调用,并且在这种情况下可能会抛出(未经检查的)异常。 该Lock实现必须标记异常类型。

void lockInterruptibly() throws InterruptedException;

image.png

  • 除非当前线程被中断否则获取锁

    • 线程中断,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。

  • 如果lock可用,则获取锁并立即返回

  • 如果锁不可用,则当前线程将出于线程调度目的而被禁用并处于休眠状态,直到发生以下两种情况之一:

    • 锁被当前线程获取; 或者
    • 其他一些线程中断当前线程,支持中断获取锁。
  • 如果当前线程:

    • 在进入此方法时设置其中断状态; 或者
    • 获取锁时中断,支持中断获取锁,然后抛出InterruptedException并清除当前线程的中断状态。
  • 实现时注意事项:

    • 在某些实现中,中断获取锁的能力可能是不可能的,如果可能的话,这可能是一项昂贵的操作。 程序员应该意识到可能是这种情况。 在这种情况下,实现应该记录。
    • 与正常方法返回相比,实现可以更倾向于响应中断
    • Lock实现可能能够检测到对锁的错误使用,例如会导致死锁的调用,并且在这种情况下可能会抛出(未经检查的)异常。 该Lock实现必须记录情况和标记异常类型。
  • 抛出InterruptedException

    • 如果当前线程在获取锁时被中断(并且支持锁获取中断)

boolean tryLock();

image.png

  • 仅当调用时锁空闲时才获取锁。
  • 如果可用,则获取锁并立即返回值为true 。 如果锁不可用,则此方法将立即返回false值。
  • 这种方法的典型用法是:
 Lock lock = ...;
 if (lock.tryLock()) {
   try {
     // manipulate protected state(处于保护状态的代码块)
   } finally {
     lock.unlock();
   }
 } else {
   // perform alternative actions(替代操作,不让线程阻塞)
 }
复制代码
  • 此用法可确保在获得锁时解锁,如果未获得锁,则不会尝试解锁。
  • 返回值:
    • 如果获得锁则为true ,否则为false

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

image.png

  • 如果在给定的等待时间内空闲并且当前线程未被中断,则获取锁
  • 如果锁可用,则此方法立即返回true值。 如果锁不可用,则当前线程将出于线程调度目的而被禁用并处于休眠状态,直到发生以下三种情况之一:
    • 锁被当前线程获取; 或者
    • 其他一些线程中断当前线程,支持中断获取锁(中断获取锁被支持); 或者
    • 经过指定的等待时间
  • 如果获得了锁,则返回值true 。
  • 如果当前线程:
    • 在进入此方法时设置其为中断状态; 或者
    • 获取锁时中断,支持中断获取锁,然后抛出InterruptedException并清除当前线程的中断状态。
  • 如果指定的等待时间过去,则返回值false 。
  • 如果时间小于或等于零,则该方法根本不会等待
  • 实现时注意事项:
    • 在某些实现中,中断获取锁的能力可能是不可能的,如果可能的话,这可能是一项昂贵的操作。 程序员 应该意识到可能是这种情况。 在这种情况下,实现应该记录。
    • 实现可以倾向于响应中断(对中断请求做出响应)而不是正常方法返回,或报告超时。
    • Lock实现可能能够检测到对锁的错误使用,例如会导致死锁的调用,并且在这种情况下可能会抛出(未经检查的)异常。 该Lock实现必须记录情况和异常类型。

参数:
time – 等待获得锁的最长时间
unit – time参数的时间单位(是枚举值
返回值:
如果获得锁则为true如果在获得锁之前等待时间已过则为false
抛出:
InterruptedException – 如果当前线程在获取锁时被中断(并且支持锁获取中断)

void unlock();

image.png

  • 释放锁
  • 实现时注意事项:
    • Lock实现通常会对哪个线程可以释放锁施加限制(通常只有锁的持有者可以释放它)并且如果违反限制可能会抛出(未经检查的)异常。 该Lock实现必须记录任何限制和异常类型。

Condition newCondition();

image.png

  • 返回绑定到此Lock实例的新Condition实例。
  • 在等待Condition之前,当前线程必须持有锁。 调用Condition.await()将在等待之前自动释放锁,并在等待返回之前重新获取锁
  • 实现时注意事项
    • Condition实例的确切操作取决于Lock实现,并且必须由该实现记录。

返回值:
Lock实例的新Condition实例
抛出:
UnsupportedOperationException – 如果此Lock实现不支持条件

小结

关于Lock和synchronized的主要区别

1.锁的获取方式:前者是通过程序代码的方式由开发者手工获取,后者是通过JVM来获取(无需开发者干预)
2.具体实现方式:前者是通过Java代码的方式来实现,后者是通过JVM底层来实现(无需开发者关注)
3.锁的释放方式:前者务必通过unlock()方法在finally块中手工释放,后者是通过JvM来释放(无需开发者关注)
4.锁的具体类型:前者提供了多种,如公平锁、非公平锁,后者与前者均提供了可重入锁

关于公平锁和非公平锁以及其他一些实现细节将在ReentrantLock的有关章节介绍

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享