java中Locks的使用详解

 更新时间:2020年03月23日 10:47:47   作者:flydean  
这篇文章主要介绍了java中Locks的使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

之前文章中我们讲到,java中实现同步的方式是使用synchronized block。在java 5中,Locks被引入了,来提供更加灵活的同步控制。

本文将会深入的讲解Lock的使用。

Lock和Synchronized Block的区别

我们在之前的Synchronized Block的文章中讲到了使用Synchronized来实现java的同步。既然Synchronized Block那么好用,为什么会引入新的Lock呢?

主要有下面几点区别:

  • synchronized block只能写在一个方法里面,而Lock的lock()和unlock()可以分别在不同的方法里面。
  • synchronized block 不支持公平锁,一旦锁被释放,任何线程都有机会获取被释放的锁。而使用 Lock APIs则可以支持公平锁。从而让等待时间最长的线程有限执行。
  • 使用synchronized block,如果线程拿不到锁,将会被Blocked。 Lock API 提供了一个tryLock() 的方法,可以判断是否可以获得lock,这样可以减少线程被阻塞的时间。
  • 当线程在等待synchronized block锁的时候,是不能被中断的。如果使用Lock API,则可以使用 lockInterruptibly()来中断线程。

Lock interface

我们来看下Lock interface的定义, Lock interface定义了下面几个主要使用的方法:

  • void lock() - 尝试获取锁,如果获取不到锁,则会进入阻塞状态。
  • void lockInterruptibly() - 和lock()很类似,但是它可以将正在阻塞的线程中断,并抛出java.lang.InterruptedException。
  • boolean tryLock() – 这是lock()的非阻塞版本,它回尝试获取锁,并立刻返回是否获取成功。
  • boolean tryLock(long timeout, TimeUnit timeUnit) – 和tryLock()很像,只是多了一个尝试获取锁的时间。
  • void unlock() – unlock实例。
  • Condition newCondition() - 生成一个和当前Lock实例绑定的Condition。

在使用Lock的时候,一定要unlocked,以避免死锁。所以,通常我们我们要在try catch中使用:

Lock lock = ...; 
lock.lock();
try {
  // access to the shared resource
} finally {
  lock.unlock();
}

除了Lock接口,还有一个ReadWriteLock接口,在其中定义了两个方法,实现了读锁和写锁分离:

  • Lock readLock() – 返回读锁
  • Lock writeLock() – 返回写锁

其中读锁可以同时被很多线程获得,只要不进行写操作。写锁同时只能被一个线程获取。

接下来,我们几个Lock的常用是实现类。

ReentrantLock

ReentrantLock是Lock的一个实现,什么是ReentrantLock(可重入锁)呢?

简单点说可重入锁就是当前线程已经获得了该锁,如果该线程的其他方法在调用的时候也需要获取该锁,那么该锁的lock数量+1,并且允许进入该方法。

不可重入锁:只判断这个锁有没有被锁上,只要被锁上申请锁的线程都会被要求等待。实现简单
可重入锁:不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当就是自己锁上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加一。

我们看下怎么使用ReentrantLock:

  public void perform() {

    lock.lock();
    try {
      counter++;
    } finally {
      lock.unlock();
    }
  }

下面是使用tryLock()的例子:

  public void performTryLock() throws InterruptedException {
    boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS);

    if(isLockAcquired) {
      try {
        counter++;
      } finally {
        lock.unlock();
      }
    }
  }

ReentrantReadWriteLock

ReentrantReadWriteLock是ReadWriteLock的一个实现。上面也讲到了ReadWriteLock主要有两个方法:

  • Read Lock - 如果没有线程获得写锁,那么可以多个线程获得读锁。
  • Write Lock - 如果没有其他的线程获得读锁和写锁,那么只有一个线程能够获得写锁。

我们看下怎么使用writeLock:

  Map<String,String> syncHashMap = new HashMap<>();
  ReadWriteLock lock = new ReentrantReadWriteLock();

  Lock writeLock = lock.writeLock();

  public void put(String key, String value) {
    try {
      writeLock.lock();
      syncHashMap.put(key, value);
    } finally {
      writeLock.unlock();
    }
  }

  public String remove(String key){
    try {
      writeLock.lock();
      return syncHashMap.remove(key);
    } finally {
      writeLock.unlock();
    }
  }

再看下怎么使用readLock:

  Lock readLock = lock.readLock();
  public String get(String key){
    try {
      readLock.lock();
      return syncHashMap.get(key);
    } finally {
      readLock.unlock();
    }
  }

  public boolean containsKey(String key) {
    try {
      readLock.lock();
      return syncHashMap.containsKey(key);
    } finally {
      readLock.unlock();
    }
  }

StampedLock

StampedLock也支持读写锁,获取锁的是会返回一个stamp,通过该stamp来进行释放锁操作。

上我们讲到了如果写锁存在的话,读锁是无法被获取的。但有时候我们读操作并不想进行加锁操作,这个时候我们就需要使用乐观读锁。

StampedLock中的stamped类似乐观锁中的版本的概念,当我们在
StampedLock中调用lock方法的时候,就会返回一个stamp,代表锁当时的状态,在乐观读锁的使用过程中,在读取数据之后,我们回去判断该stamp状态是否变化,如果变化了就说明该stamp被另外的write线程修改了,这说明我们之前的读是无效的,这个时候我们就需要将乐观读锁升级为读锁,来重新获取数据。

我们举个例子,先看下write排它锁的情况:

  private double x, y;
  private final StampedLock sl = new StampedLock();

  void move(double deltaX, double deltaY) { // an exclusively locked method
    long stamp = sl.writeLock();
    try {
      x += deltaX;
      y += deltaY;
    } finally {
      sl.unlockWrite(stamp);
    }
  }

再看下乐观读锁的情况:

  double distanceFromOrigin() { // A read-only method
    long stamp = sl.tryOptimisticRead();
    double currentX = x, currentY = y;
    if (!sl.validate(stamp)) {
      stamp = sl.readLock();
      try {
        currentX = x;
        currentY = y;
      } finally {
        sl.unlockRead(stamp);
      }
    }
    return Math.sqrt(currentX * currentX + currentY * currentY);
  }

上面使用tryOptimisticRead()来尝试获取乐观读锁,然后通过sl.validate(stamp)来判断该stamp是否被改变,如果改变了,说明之前的read是无效的,那么需要重新来读取。

最后,StampedLock还提供了一个将read锁和乐观读锁升级为write锁的功能:

  void moveIfAtOrigin(double newX, double newY) { // upgrade
    // Could instead start with optimistic, not read mode
    long stamp = sl.readLock();
    try {
      while (x == 0.0 && y == 0.0) {
        long ws = sl.tryConvertToWriteLock(stamp);
        if (ws != 0L) {
          stamp = ws;
          x = newX;
          y = newY;
          break;
        }
        else {
          sl.unlockRead(stamp);
          stamp = sl.writeLock();
        }
      }
    } finally {
      sl.unlock(stamp);
    }
  }

上面的例子是通过使用tryConvertToWriteLock(stamp)来实现升级的。

Conditions

上面讲Lock接口的时候有提到其中的一个方法:

Condition newCondition();

Condition提供了await和signal方法,类似于Object中的wait和notify。

不同的是Condition提供了更加细粒度的等待集划分。我们举个例子:

public class ConditionUsage {
  final Lock lock = new ReentrantLock();
  final Condition notFull = lock.newCondition();
  final Condition notEmpty = lock.newCondition();

  final Object[] items = new Object[100];
  int putptr, takeptr, count;

  public void put(Object x) throws InterruptedException {
    lock.lock();
    try {
      while (count == items.length)
        notFull.await();
      items[putptr] = x;
      if (++putptr == items.length) putptr = 0;
      ++count;
      notEmpty.signal();
    } finally {
      lock.unlock();
    }
  }

  public Object take() throws InterruptedException {
    lock.lock();
    try {
      while (count == 0)
        notEmpty.await();
      Object x = items[takeptr];
      if (++takeptr == items.length) takeptr = 0;
      --count;
      notFull.signal();
      return x;
    } finally {
      lock.unlock();
    }
  }
}

上面的例子实现了一个ArrayBlockingQueue,我们可以看到在同一个Lock实例中,创建了两个Condition,分别代表队列未满,队列未空。通过这种细粒度的划分,我们可以更好的控制业务逻辑。

本文的例子可以参考https://github.com/ddean2009/learn-java-concurrency/tree/master/Locks

到此这篇关于java中Locks的使用详解的文章就介绍到这了,更多相关java Locks内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java代码实现酒店管理系统

    Java代码实现酒店管理系统

    这篇文章主要为大家详细介绍了Java代码实现酒店管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • Java并发容器之ConcurrentLinkedQueue详解

    Java并发容器之ConcurrentLinkedQueue详解

    这篇文章主要介绍了Java并发容器之ConcurrentLinkedQueue详解,加锁队列的实现较为简单,这里就略过,我们来重点来解读一下非阻塞队列,
    从点到面, 下面我们来看下非阻塞队列经典实现类ConcurrentLinkedQueue,需要的朋友可以参考下
    2023-12-12
  • IDEA在plugins里搜不到mybatisx插件的解决方法

    IDEA在plugins里搜不到mybatisx插件的解决方法

    本文主要介绍了IDEA在plugins里搜不到mybatisx插件的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • 微服务SpringBoot整合Jasypt加密工具的场景分析

    微服务SpringBoot整合Jasypt加密工具的场景分析

    Jasypt是Java加密工具包,能支持对密码的哈希加密,对文本和二进制数据的对称加解密,还能集成SpringBoot项目对配置文件中的密钥进行加密存储,这篇文章主要介绍了微服务SpringBoot整合Jasypt加密工具,需要的朋友可以参考下
    2022-10-10
  • Zookeeper中如何解决zookeeper.out文件输出位置问题

    Zookeeper中如何解决zookeeper.out文件输出位置问题

    这篇文章主要介绍了Zookeeper中如何解决zookeeper.out文件输出位置问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • 在SpringBoot 中从application.yml中获取自定义常量方式

    在SpringBoot 中从application.yml中获取自定义常量方式

    这篇文章主要介绍了在SpringBoot 中从application.yml中获取自定义常量方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-04-04
  • 深入解析Apache Kafka实时流处理平台

    深入解析Apache Kafka实时流处理平台

    这篇文章主要为大家介绍了Apache Kafka实时流处理平台深入解析,从基本概念到实战操作详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01
  • Java毕业设计实战之健身器材商城系统的实现

    Java毕业设计实战之健身器材商城系统的实现

    只学书上的理论是远远不够的,只有在实战中才能获得能力的提升,本篇文章手把手带你用java+Jdbc+Servlet+Ajax+Fileupload+mysql实现健身器材商城系统,大家可以在过程中查缺补漏,提升水平
    2022-03-03
  • 聊聊Java Double相加出现的怪事

    聊聊Java Double相加出现的怪事

    这篇文章主要介绍了Java Double相加出现的怪事,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • 详解Java关于时间格式化的方法

    详解Java关于时间格式化的方法

    这篇文章主要介绍了详解Java关于时间格式化的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09

最新评论