Java 中的锁之Lock接口

锁是控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源(但是有些锁是允许多个并发线程的访问,如读写锁),在Lock接口出现之前,Java程序靠synchronized关键字实现锁功能的。Java SE5之后,并发包里增加了Lock 接口(以及相关实现类)用来实现锁功能,提供类似的同步功能,缺少synchronized 隐身释放锁的便捷性,但是拥有锁的获取与释放的可操作性、可中断获取锁以及超时获取锁【简单介绍一下超时获取锁,通过tryacquire()获取同步状态成功(根据返回值),且当前节点前驱为头节点,则返回;获取同步状态失败则重新计算是否超时,没超时重新去获取同步状态,超时则放回false,一般情况下不会进入自旋】等多种同步特性。不要将锁的获取过程写在try 快中,因为如果在获取锁的过程时(自定义锁的实现)发生异常,也会导致锁无故释放。

Lock接口一些关键特性

1.尝试非阻塞性获取锁 【如独占式超时获取锁】
2.能被中断的获取锁:当获取到锁的线程被中断时,中断异常将会被抛出,同时释放锁。 【独占式共享式获取锁等都支持】
3.超时获取锁:在指定的时间获取锁,如果截止时间还无法获取锁,则返回


Lock的API

1.void lock();获得后从该方法返回
2.void lockInterruptibly() 可中断的获取锁
3.boolean tryLock():尝试非阻塞方式获取锁,成功获取返回true
4.void unlock():释放锁

Attention:Lock接口的实现基本都是通过聚合一个同步器的子类完成线程访问控制。即同步器是实现锁的关键,利用同步器实现搜索的语义。

同步器

队列同步器简称同步器【abstactQueuedSynchronizer],是用来构建锁或则其他同步组件的基础框架,通过内置的FIFO队列来完成资源获取线程的排队工作。同步器主要使用方式是继承,子类推荐被定义为自定义同步组件的静态内部类。同步器自身没有实现同步接口,仅仅定义获取和释放的方法供自定义同步组件调用,因此可以独占式获取同步状态也可以共享式获取同步状态。

同步器的接口

同步器的设计是基于模板方法模式的,使用者需要继承同步器并重写指定的方法,然后组合在自定义同步组件中,调用同步器提供的模块方法,而这些模块方法进一步调用使用者重写的方法。

同步器重写的方法

方法名称描述
protected boolean tryAcquire(int arg)独占式获取同步状态
protected boolean tryRelase(int arg)独占式释放同步状态
protected boolean tryAcquireShared(int flag)共享式获取同步状态
protected boolean tryRelaseShare(int arg)共享式释放同步状态
protected boolean isHeldExclusive()当前同步器是否在独占模式下被线程占用
同步器提供的模板方法
方法名描述
void acquire(int arg)独占获取同步状态
void acquireInterruptibly(int arg)同上但是相应中断
tryAcquireNanos(int arg,long nanos)超时控制

同步器提供的模板方法大致分为三类:独占式获取/释放同步状态、共享式获取与释放同步状态、查询同步队列中的现成状态。

队列同步器的实现分析

1.同步队列
同步器依赖内部的同步队列(一个FIFO双向对列),当前线程获取同步状态失败时,同步器将当前线程以及等待状态等信息构造成一个节点加入同步队列,同时会阻塞同步线程,当同步状态释放时候,会把首结点的现场唤醒,使其再次获得同步状态。
1.独占式同步状态的获取与释放
主要完成同步状态获取、结点构造、加入同步队列、以及在同步队列中自旋的相关工作。
在同步状态获取中,通过tryAquire() 中if(compareAndSetSate(0,1))
setExclusiveOwnerThread(Thread.currentThread());

Attention:
Question 1:当多个线程无法获取同步状态需要加入同步队列中,如何保证线程安全性?
Answer:同步器提供一个基于CAS的设置尾结点的方法,他需要传递当前线程认为的尾结点和当前结点,只有设置成功后,当前结点才能与之前尾结点建立关联,具体实现通过addwaiter()方法添加,enq实现节点的添加请求串行化
Question2:被阻塞线程如何被唤醒和推出自旋
Answer:节点进入同步队列以后将进入一个自旋的过程,由acquireQueued()函数进行死循环当条件满足,就可以从这个自旋过程中推出,所谓的条件1)阻塞线程被中断 2)前驱节点出队唤醒当前线程,当前线程前驱结点是头结点,并且获得资源同步状态

2.共享式同步状态的获取与释放
可以设置起始同步状态,并可以根据同步状态值判断是否获取成功

共享式获取与独占式获取最主要的区别在于同一时刻能否有多个线程同时获取到同步状态。其他共享式的访问均被允许。在模板方法中,acquireshare(int arg)方法调用tryAcquiresShared(),当返回值大于等于0,表示能够获得同步状态,。在共享式自旋中,成功获得同步状态并退出自旋的条件是tryAcquiresShare()方法大于等于0.自旋过程中,如果前驱节点为头节点,则尝试获取同步状态。获取成功则退出

3.自定义实例独占锁

public class Mutex implements Lock {
 
  // 静态内部类,自定义同步器
 private static class Sync extends AbstractQueuedSynchronizer {
  // 是否处于占用状态
  @Override
  protected boolean isHeldExclusively() {
   return getState() == 1;
  }
 
  // 当状态为0的时候获取锁
  @Override
  protected boolean tryAcquire(int acquires) {
   if (compareAndSetState(0, 1)) {
    setExclusiveOwnerThread(Thread.currentThread());
    return true;
   }
   return false;
  }
 
  // 释放锁,将状态设置为0
  @Override
  protected boolean tryRelease(int releases) {
   if(getState() == 0) throw new IllegalMonitorStateException();
   setExclusiveOwnerThread(null);
   setState(0);
   return true;
  }
 
  // 返回一个Condition,每个condition都包含了一个condition队列
  Condition newCondition() { return new ConditionObject(); }
 }
 // 仅需要将操作代理到Sync上即可
 private final Sync sync = new Sync();
 @Override
 public void lock() { sync.acquire(1); }
 @Override
 public boolean tryLock() { return sync.tryAcquire(1); }
 @Override
 public void unlock() { sync.release(1); }
 @Override
 public Condition newCondition() { return sync.newCondition(); }
 public boolean isLocked() { return sync.isHeldExclusively(); }
 public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
 @Override
 public void lockInterruptibly() throws InterruptedException {
  sync.acquireInterruptibly(1);
 }
 @Override
 public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
  return sync.tryAcquireNanos(1, unit.toNanos(timeout));
 }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: JavaLock接口Java并发编程的一种实现方式,它提供了比传统的synchronized块更高级别的定机制。 Lock接口的使用场景如下: 1. 多个线程同时访问共享资源时,需要确保同一时刻只有一个线程可以访问该资源。 2. 当线程需要访问一个共享资源时,需要先获取该资源的。如果该资源已经被其他线程定,则当前线程会被阻塞,直到获取到该资源的为止。 3. 当线程访问完共享资源后,需要释放该资源的,以便其他线程可以继续访问该资源。 在JavaLock接口的主要实现类是ReentrantLock。使用Lock接口可以实现更细粒度的控制,比如可以指定定的超时时间、可重入性等。 下面是Lock接口的使用示例: ``` import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockExample { private final Lock lock = new ReentrantLock(); public void doSomething() { lock.lock(); try { // 执行需要定的操作 } finally { lock.unlock(); } } } ``` 在上述代码,ReentrantLock实例被用来保护某个需要定的操作,doSomething()方法获取之后执行需要定的操作,最后释放。 总之,使用Lock接口可以更加灵活地控制多线程并发访问共享资源的行为,从而提高程序的并发性能。 ### 回答2: 在Java分布式环境Lock接口可以用于解决多个线程之间的并发访问问题,并确保资源的互斥访问。Lock接口的使用场景如下: 1. 多线程编程:在多线程编程,如果多个线程同时访问共享资源,可能会导致数据的不一致性或竞态条件的出现。通过使用Lock接口,可以保证在同一时刻只有一个线程可以访问到共享资源,从而避免竞争条件的发生。 2. 分布式缓存:在分布式缓存,多个节点可能同时访问同一个缓存数据。使用Lock接口可以确保在任意时刻只有一个节点可以对缓存数据进行修改或读取操作,从而保证数据的一致性和可靠性。 3. 分布式任务调度:在分布式任务调度,多个节点可能同时竞争执行同一个任务。通过使用Lock接口,可以保证只有一个节点能够获得任务的执行权,从而避免重复执行或竞争问题的发生。 4. 分布式事务:在分布式事务,多个节点可能同时操作同一个数据源。通过使用Lock接口,在进行事务提交或回滚时可以确保同一数据只能被一个节点访问,从而保证数据的一致性和完整性。 总之,Lock接口Java分布式环境可以用于解决并发访问问题,确保资源的互斥访问,并保证数据的一致性和可靠性。 ### 回答3: 在Java分布式环境Lock接口的使用场景主要是为了保证多个线程或者多个进程之间的数据操作的安全性和一致性。 首先,Lock接口可以用于保护共享资源的访问。在分布式系统,多个处理节点可能同时访问同一个共享资源,而Lock接口可以提供互斥机制,确保同时只能有一个节点能够对资源进行操作。比如,在一个分布式数据库,多个节点同时对同一条数据进行写操作,使用Lock接口可以保证在同一时间内只有一个节点能够成功写入,避免了数据的冲突。 其次,Lock接口也可以用于实现分布式的任务调度。在一个分布式系统,多个节点可能需要按照一定的次序执行某些任务,而Lock接口提供了可重入的互斥机制,可以实现节点之间对任务的顺序控制。比如,一个分布式任务调度系统,多个节点需要根据优先级依次执行任务,使用Lock接口可以确保按照优先级顺序对任务进行调度,避免了任务的乱序执行。 另外,Lock接口还可以用于实现分布式的事务处理。在分布式系统,多个节点可能需要同时对多个资源进行操作,而Lock接口可以提供分布式事务机制,保证所有操作都能够成功完成或者回滚。比如,一个分布式订单处理系统,多个节点需要同时对订单和库存进行操作,使用Lock接口可以保证在同一时间内只有一个节点能够对订单和库存进行修改,避免了订单和库存的不一致。 总之,Lock接口Java分布式环境的使用场景主要是为了保证数据操作的安全性和一致性,包括保护共享资源的访问、实现分布式的任务调度和实现分布式的事务处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值