JAVA--Lock接口详解

本文详细介绍了Java中的Lock接口及其核心实现ReentrantLock等,并对比了synchronized关键字的区别。此外,文章还深入探讨了Lock接口的各种方法,如lock、tryLock等,并解释了内存同步和实现注意事项。

1. Lock接口已知实现类

ReentrantLock,  

ReentrantReadWriteLock.ReadLock,  

ReentrantReadWriteLock.WriteLock

2. Lock接口解析

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。

锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock 的读取锁。

synchronized 方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。

虽然 synchronized 方法和语句的范围机制使得使用监视器锁编程方便了很多,而且还帮助避免了很多涉及到锁的常见编程错误,但有时也需要以更为灵活的方式使用锁。例如,某些遍历并发访问的数据结果的算法要求使用 "hand-over-hand" 或 "chain locking":获取节点 A 的锁,然后再获取节点 B 的锁,然后释放 A 并获取 C,然后释放 B 并获取 D,依此类推。Lock 接口的实现允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁,从而支持使用这种技术。

随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:

Lock l = ...; 
     l.lock();
     try {
         // access the resource protected by this lock
     } finally {
         l.unlock();
     }

锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。

Lock 实现提供了使用 synchronized 方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试 (tryLock())、一个获取可中断锁的尝试 (lockInterruptibly()) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit))。

Lock 类还可以提供与隐式监视器锁完全不同的行为和语义,如保证排序、非重入用法或死锁检测。如果某个实现提供了这样特殊的语义,则该实现必须对这些语义加以记录。

注意,Lock 实例只是普通的对象,其本身可以在 synchronized 语句中作为目标使用。获取 Lock实例的监视器锁与调用该实例的任何 lock() 方法没有特别的关系。为了避免混淆,建议除了在其自身的实现中之外,决不要以这种方式使用 Lock 实例。也就是说lock和synchronized不要混用。

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

3. 内存同步

所有 Lock 实现都必须 实施与内置监视器锁提供的相同内存同步语义,如 The Java Language Specification, Third Edition (17.4 Memory Model) 中所描述的:

  • 成功的 lock 操作与成功的 Lock 操作具有同样的内存同步效应。
  • 成功的 unlock 操作与成功的 Unlock 操作具有同样的内存同步效应。 

不成功的锁定与取消锁定操作以及重入锁定/取消锁定操作都不需要任何内存同步效果。

4. 实现注意事项

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

由于中断通常意味着取消,而通常又很少进行中断检查,因此,相对于普通方法返回而言,实现可能更喜欢响应某个中断。即使出现在另一个操作后的中断可能会释放线程锁时也是如此。实现应记录此行为。

5. 方法详解

lock

void lock()

获取锁。

如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态。

实现注意事项

Lock 实现可能能够检测到锁的错误使用,比如会导致死锁的调用,在那种环境下还可能抛出一个 (unchecked) 异常。Lock 实现必须对环境和异常类型进行记录

 

lockInterruptibly

void lockInterruptibly() throws InterruptedException

如果当前线程未被中断,则获取锁。 

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

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

  • 锁由当前线程获得
  • 其他某个线程中断当前线程,并且支持对锁获取的中断。

如果当前线程:

  • 在进入此方法时已经设置了该线程的中断状态;
  • 在获取锁时被中断,并且支持对锁获取的中断,

则将抛出 InterruptedException,并清除当前线程的已中断状态。 

实现注意事项

在某些实现中可能无法中断锁获取,即使可能,该操作的开销也很大。程序员应该知道可能会发生这种情况。在这种情况下,该实现应该对此进行记录。

相对于普通方法返回而言,实现可能更喜欢响应某个中断。

Lock 实现可能可以检测锁的错误用法,例如,某个调用可能导致死锁,在特定的环境中可能抛出(未经检查的)异常。该 Lock 实现必须对环境和异常类型进行记录。

抛出:

InterruptedException - 如果在获取锁时,当前线程被中断(并且支持对锁获取的中断)。

 

tryLock

boolean tryLock()

仅在调用时锁为空闲状态才获取该锁。

如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false

此方法的典型使用语句如下:

Lock lock = ...;
      if (lock.tryLock()) {
          try {
              // manipulate protected state
          } finally {
              lock.unlock();
          }
      } else {
          // perform alternative actions
      }

此用法可确保如果获取了锁,则会释放锁,如果未获取锁,则不会试图将其释放。

返回:

如果获取了锁,则返回 true;否则返回 false

 

tryLock

boolean tryLock(long time,
                TimeUnit unit)
                throws InterruptedException

如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。

如果锁可用,则此方法将立即返回值 true。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在发生以下三种情况之一前,该线程将一直处于休眠状态:

  • 锁由当前线程获得;或者
  • 其他某个线程中断当前线程,并且支持对锁获取的中断;或者
  • 已超过指定的等待时间

如果获得了锁,则返回值 true

如果当前线程:

  • 在进入此方法时已经设置了该线程的中断状态;或者
  • 在获取锁时被中断,并且支持对锁获取的中断,

则将抛出 InterruptedException,并会清除当前线程的已中断状态。 

如果超过了指定的等待时间,则将返回值 false。如果 time 小于等于 0,该方法将完全不等待。

实现注意事项

在某些实现中可能无法中断锁获取,即使可能,该操作的开销也很大。程序员应该知道可能会发生这种情况。在这种情况下,该实现应该对此进行记录。

相对于普通方法返回而言,实现可能更喜欢响应某个中断,或者报告出现超时情况。

Lock 实现可能可以检测锁的错误用法,例如,某个调用可能导致死锁,在特定的环境中可能抛出(未经检查的)异常。该 Lock 实现必须对环境和异常类型进行记录。

参数:

time - 等待锁的最长时间

unit - time 参数的时间单位

返回:

如果获得了锁,则返回 true;如果在获取锁前超过了等待时间,则返回 false

抛出:

InterruptedException - 如果在获取锁时,当前线程被中断(并且支持对锁获取的中断)

 

unlock

void unlock()

释放锁。

实现注意事项

Lock 实现通常对哪个线程可以释放锁施加了限制(通常只有锁的保持者可以释放它),如果违背了这个限制,可能会抛出(未经检查的)异常。该 Lock 实现必须对所有限制和异常类型进行记录。

 

newCondition

Condition newCondition()

返回绑定到此 Lock 实例的新 Condition 实例。

在等待条件前,锁必须由当前线程保持。调用 Condition.await() 将在等待前以原子方式释放锁,并在等待返回前重新获取锁。

实现注意事项

Condition 实例的具体操作依赖于 Lock 实现,并且该实现必须对此加以记录。

返回:

用于此 Lock 实例的新 Condition 实例

抛出:

UnsupportedOperationException - 如果此 Lock 实现不支持条件

 

转载于:https://my.oschina.net/hunglish/blog/753808

<think>好的,我现在要详细解释Java中的Lock接口和synchronized锁机制,同时结合用户提供的引用内容。首先,我需要明确用户的需求:他们想了解Lock接口的详细用法和实现,以及它与synchronized的区别和各自的锁机制。 首先,根据引用[1]和[2],Lock接口Java并发包的一部分,提供了比synchronized更灵活的锁控制。需要说明Lock的基本用法,比如lock()、unlock(),以及它的实现类如ReentrantLock。同时,对比synchronized,在引用[3]和[4]中提到synchronized的底层实现和优化,比如偏向锁、轻量级锁等,这些也需要涵盖进去。 接下来,用户可能希望了解两者的区别和适用场景。引用[2]指出Lock提供了更多控制方式,比如可中断、超时获取锁、公平锁等,而synchronized是隐式获取和释放锁。需要详细对比这些特性,并给出使用示例。 然后,关于Lock接口的实现,需要解释其内部机制,比如AQS(AbstractQueuedSynchronizer),这是Java并发包中实现锁的基础。虽然引用中没有直接提到AQS,但结合知识,ReentrantLock是基于AQS实现的,应该补充这一点,但注意用户提供的引用中没有相关说明,所以可能需要避免超出引用范围。 对于synchronized的锁升级机制,引用[3]和[4]详细说明了偏向锁、轻量级锁、重量级锁的过程,需要分步骤解释,并结合jdk1.6后的优化措施。 还需要考虑代码示例,展示Lock和synchronized的具体使用方式,比如Lock的标准try-finally块,以及synchronized修饰方法或代码块。 最后,生成相关问题时要围绕两者的区别、使用场景、性能比较、锁升级机制等方面提问,确保问题具有针对性,帮助用户深入理解。 在组织内容时,需要保持结构清晰,分点讲解Lock和synchronized,再对比它们的区别,最后总结应用场景。同时,确保所有LaTeX格式正确,行内公式用$...$,独立公式用$$...$$,但当前内容可能较少涉及数学公式,需要检查是否有必要添加。 现在检查是否有遗漏的部分,比如Lock接口的具体方法,如tryLock()、lockInterruptibly()等,以及Condition的使用,这些在引用中没有提到,但属于Lock接口的重要部分,需要包含进去。不过根据用户提供的引用,可能主要侧重于基本区别和机制,因此需要平衡详细度和引用内容。 总结,回答应包括: 1. Lock接口的用法和实现 2. synchronized的机制和优化 3. 两者的对比 4. 应用场景 5. 相关问题 确保每个部分都引用正确的来源,例如锁升级机制引用[3][4],Lock的优势引用[1][2]等。</think>### Lock接口与synchronized锁机制详解 #### 一、Lock接口的用法与实现 Lock接口Java并发包(JUC)中定义的锁机制,提供比synchronized更灵活的控制方式。核心方法包括: - `lock()`:获取锁,若锁被占用则阻塞。 - `unlock()`:释放锁。 - `tryLock()`:尝试非阻塞获取锁,成功返回`true`。 - `lockInterruptibly()`:可响应中断的锁获取。 **示例代码**: ```java Lock lock = new ReentrantLock(); lock.lock(); try { // 同步代码块 } finally { lock.unlock(); } ``` Lock的实现类如`ReentrantLock`支持**公平锁**和**非公平锁**,通过构造参数指定。公平锁按等待顺序分配锁,非公平锁允许插队以提高吞吐量[^1][^2]。 #### 二、synchronized的锁机制与优化 synchronized是JVM层面的内置锁,通过`monitorenter`和`monitorexit`指令实现。其锁升级机制(JDK 1.6+优化)包括: 1. **偏向锁**:首次访问时标记线程ID,后续直接进入同步代码块[^4]。 2. **轻量级锁**:当多线程竞争时,通过CAS操作尝试获取锁。 3. **重量级锁**:竞争激烈时升级为操作系统级别的Mutex Lock,涉及用户态到内核态的切换[^3]。 **示例代码**: ```java public synchronized void method() { // 同步方法 } // 或同步代码块 public void method() { synchronized(this) { // 同步区域 } } ``` #### 三、Lock与synchronized的对比 | 特性 | Lock接口 | synchronized | |---------------------|-----------------------------------|-----------------------------| | **锁获取方式** | 显式调用`lock()`/`unlock()` | 隐式,通过代码块或方法修饰 | | **中断响应** | 支持`lockInterruptibly()` | 不支持 | | **超时机制** | 支持`tryLock(long time, TimeUnit unit)` | 不支持 | | **公平性** | 可配置公平锁或非公平锁 | 仅非公平锁 | | **锁绑定多个条件** | 通过`Condition`实现多条件队列 | 仅一个等待队列 | | **性能** | 高竞争场景下更优 | 低竞争场景下优化更好 | #### 四、应用场景 - **synchronized**:适合简单的同步需求,如单线程占主导的低竞争场景。 - **Lock接口**:适合需要精细控制的场景,如超时锁、可中断锁、公平锁等[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值