1.简介

在许多情况下,使用隐式锁定就足够了。 有时,我们将需要更复杂的功能。 在这种情况下, java.util.concurrent.locks包为我们提供了锁定对象。 当涉及到内存同步时,这些锁的内部机制与隐式锁相同。 区别在于显式锁提供了其他功能。

与隐式同步相比,主要优点或改进是:

  • 通过读取或写入来分离锁。
  • 一些锁允许并发访问共享资源( ReadWriteLock )。
  • 获取锁的不同方式:
    • 阻塞:lock()

2.锁对象的分类

锁定对象实现以下两个接口之一:

  • Lock :定义锁对象必须实现的基本功能。 基本上,这意味着获取和释放锁。 与隐式锁相反,此锁允许以非阻塞或可中断的方式(除阻塞方式外)获取锁。 主要实现:

    • 重入锁
  • ReadWriteLock :它保留一对锁,一个锁用于只读操作,另一个锁用于写操作。 读锁可以由不同的读取器线程同时获取(只要写锁尚未获取资源),而写锁是互斥的。 这样,只要没有写操作,我们就可以让多个线程同时读取资源。 主要实现:

    • 重入ReadWriteLock

下面的类图显示了不同锁类之间的关系:

3.重入锁

此锁的工作方式与同步块相同。 只要一个线程尚未被另一个线程获取,它就会获取该锁,并且直到调用unlock之前,它才会释放该锁。 如果另一个线程已经获取了该锁,则尝试获取该锁的线程将被阻塞,直到另一个线程释放它为止。

我们将从一个没有锁的简单示例开始,然后我们将添加一个可重入锁以查看其工作方式。

public class NoLocking {public static void main(String[] args) {Worker worker = new Worker();Thread t1 = new Thread(worker, "Thread-1");Thread t2 = new Thread(worker, "Thread-2");t1.start();t2.start();}private static class Worker implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " - 1");System.out.println(Thread.currentThread().getName() + " - 2");System.out.println(Thread.currentThread().getName() + " - 3");}}
}

由于上面的代码未同步,因此线程将被交错。 让我们看一下输出:

Thread-2 - 1
Thread-1 - 1
Thread-1 - 2
Thread-1 - 3
Thread-2 - 2
Thread-2 - 3

现在,我们将添加一个可重入锁,以序列化对run方法的访问:

public class ReentrantLockExample {public static void main(String[] args) {Worker worker = new Worker();Thread t1 = new Thread(worker, "Thread-1");Thread t2 = new Thread(worker, "Thread-2");t1.start();t2.start();}private static class Worker implements Runnable {private final ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {lock.lock();try {System.out.println(Thread.currentThread().getName() + " - 1");System.out.println(Thread.currentThread().getName() + " - 2");System.out.println(Thread.currentThread().getName() + " - 3");} finally {lock.unlock();}}}
}

上面的代码将安全地执行,而不会交错线程。 您可能意识到我们可以使用同步块,并且效果是相同的。 现在出现的问题是可重入锁提供给我们什么好处?

下面介绍了使用这种类型的锁的主要优点:

  • 通过实现Lock接口提供了获取锁的其他方式:

    • lockInterruptible :如果另一个线程拥有锁,则当前线程将尝试获取解除锁定并被阻塞,例如使用lock()方法。
  • ReentrantLock类提供的其他方法,主要用于监视或测试。 例如, getHoldCountisHeldByCurrentThread方法。

让我们看一个使用tryLock的示例,然后再继续下一个锁类。

3.1尝试获取锁

在下面的示例中,我们有两个线程,试图获取相同的两个锁。

一个线程获取lock2 ,然后阻止尝试获取lock1

public void lockBlocking() {LOGGER.info("{}|Trying to acquire lock2...", Thread.currentThread().getName());lock2.lock();try {LOGGER.info("{}|Lock2 acquired. Trying to acquire lock1...", Thread.currentThread().getName());lock1.lock();LOGGER.info("{}|Both locks acquired", Thread.currentThread().getName());} finally {lock1.unlock();lock2.unlock();}
}

另一个线程获取lock1 ,然后尝试获取lock2

public void lockWithTry() {LOGGER.info("{}|Trying to acquire lock1...", Thread.currentThread().getName());lock1.lock();try {LOGGER.info("{}|Lock1 acquired. Trying to acquire lock2...", Thread.currentThread().getName());boolean acquired = lock2.tryLock(4, TimeUnit.SECONDS);if (acquired) {try {LOGGER.info("{}|Both locks acquired", Thread.currentThread().getName());} finally {lock2.unlock();}}else {LOGGER.info("{}|Failed acquiring lock2. Releasing lock1", Thread.currentThread().getName());}} catch (InterruptedException e) {//handle interrupted exception} finally {lock1.unlock();}
}

使用标准锁定方法,这将导致死锁,因为每个线程将永远等待另一个线程释放该锁。 但是,这次我们尝试使用tryLock指定超时来获取它。 如果四秒钟后仍未成功,它将取消操作并释放第一把锁。 这将允许另一个线程解锁并获得两个锁。

让我们看完整的例子:

public class TryLock {private static final Logger LOGGER = LoggerFactory.getLogger(TryLock.class);private final ReentrantLock lock1 = new ReentrantLock();private final ReentrantLock lock2 = new ReentrantLock();public static void main(String[] args) {TryLock app = new TryLock();Thread t1 = new Thread(new Worker1(app), "Thread-1");Thread t2 = new Thread(new Worker2(app), "Thread-2");t1.start();t2.start();}public void lockWithTry() {LOGGER.info("{}|Trying to acquire lock1...", Thread.currentThread().getName());lock1.lock();try {LOGGER.info("{}|Lock1 acquired. Trying to acquire lock2...", Thread.currentThread().getName());boolean acquired = lock2.tryLock(4, TimeUnit.SECONDS);if (acquired) {try {LOGGER.info("{}|Both locks acquired", Thread.currentThread().getName());} finally {lock2.unlock();}}else {LOGGER.info("{}|Failed acquiring lock2. Releasing lock1", Thread.currentThread().getName());}} catch (InterruptedException e) {//handle interrupted exception} finally {lock1.unlock();}}public void lockBlocking() {LOGGER.info("{}|Trying to acquire lock2...", Thread.currentThread().getName());lock2.lock();try {LOGGER.info("{}|Lock2 acquired. Trying to acquire lock1...", Thread.currentThread().getName());lock1.lock();LOGGER.info("{}|Both locks acquired", Thread.currentThread().getName());} finally {lock1.unlock();lock2.unlock();}}private static class Worker1 implements Runnable {private final TryLock app;public Worker1(TryLock app) {this.app = app;}@Overridepublic void run() {app.lockWithTry();}}private static class Worker2 implements Runnable {private final TryLock app;public Worker2(TryLock app) {this.app = app;}@Overridepublic void run() {app.lockBlocking();}}
}

如果执行代码,将产生以下输出:

13:06:38,654|Thread-2|Trying to acquire lock2...
13:06:38,654|Thread-1|Trying to acquire lock1...
13:06:38,655|Thread-2|Lock2 acquired. Trying to acquire lock1...
13:06:38,655|Thread-1|Lock1 acquired. Trying to acquire lock2...
13:06:42,658|Thread-1|Failed acquiring lock2. Releasing lock1
13:06:42,658|Thread-2|Both locks acquired

在第四行之后,每个线程都已获取一个锁,并且在尝试获取另一个锁时被阻塞。 在下一行,您会注意到四秒钟的间隔。 自超时以来,第一个线程无法获取锁并释放它已经获取的锁,从而允许第二个线程继续。

4. ReentrantReadWriteLock

这种类型的锁保留一对内部锁( ReadLockWriteLock )。 如接口所述,此锁允许多个线程同时从资源读取。 当资源具有频繁读取但很少写入的资源时,这特别方便。 只要没有需要写的线程,资源就将被并发访问。

以下示例显示了三个线程同时从共享资源读取。 当第四个线程需要写入时,它将排他地锁定资源,从而防止读取线程在写入时访问该资源。 一旦写入完成并释放了锁定,所有读取器线程将继续并发访问资源:

public class ReadWriteLockExample {private static final Logger LOGGER = LoggerFactory.getLogger(ReadWriteLockExample.class);final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();private Data data = new Data("default value");public static void main(String[] args) {ReadWriteLockExample example = new ReadWriteLockExample();example.start();}private void start() {ExecutorService service = Executors.newFixedThreadPool(4);for (int i=0; i<3; i++) service.execute(new ReadWorker());service.execute(new WriteWorker());service.shutdown();}class ReadWorker implements Runnable {@Overridepublic void run() {for (int i = 0; i < 2; i++) {readWriteLock.readLock().lock();try {LOGGER.info("{}|Read lock acquired", Thread.currentThread().getName());Thread.sleep(3000);LOGGER.info("{}|Reading data: {}", Thread.currentThread().getName(), data.getValue());} catch (InterruptedException e) {//handle interrupted} finally {readWriteLock.readLock().unlock();}}}}class WriteWorker implements Runnable {@Overridepublic void run() {readWriteLock.writeLock().lock();try {LOGGER.info("{}|Write lock acquired", Thread.currentThread().getName());Thread.sleep(3000);data.setValue("changed value");LOGGER.info("{}|Writing data: changed value", Thread.currentThread().getName());} catch (InterruptedException e) {//handle interrupted} finally {readWriteLock.writeLock().unlock();}}}
}

控制台输出显示结果:

11:55:01,632|pool-1-thread-1|Read lock acquired
11:55:01,632|pool-1-thread-2|Read lock acquired
11:55:01,632|pool-1-thread-3|Read lock acquired
11:55:04,633|pool-1-thread-3|Reading data: default value
11:55:04,633|pool-1-thread-1|Reading data: default value
11:55:04,633|pool-1-thread-2|Reading data: default value
11:55:04,634|pool-1-thread-4|Write lock acquired
11:55:07,634|pool-1-thread-4|Writing data: changed value
11:55:07,634|pool-1-thread-3|Read lock acquired
11:55:07,635|pool-1-thread-1|Read lock acquired
11:55:07,635|pool-1-thread-2|Read lock acquired
11:55:10,636|pool-1-thread-3|Reading data: changed value
11:55:10,636|pool-1-thread-1|Reading data: changed value
11:55:10,636|pool-1-thread-2|Reading data: changed value

如您所见,当编写器线程获得写锁定(线程4)时,其他任何线程都无法访问该资源。

5.结论

这篇文章展示了显式锁的主要实现方式,并解释了与隐式锁有关的一些改进功能。 这篇文章是Java Concurrency Tutorial系列的一部分。 检查此处以阅读本教程的其余部分。

  • 您可以在Github上找到源代码。

翻译自: https://www.javacodegeeks.com/2015/02/java-concurrency-tutorial-locking-explicit-locks.html

Java并发教程–锁定:显式锁定相关推荐

  1. Java并发编程之显式锁(Lock)使用

    又是一个基于AQS好用的类,看来下次有必要看看AQS了,正好又是放假. 既然叫显式锁,必然也有隐式锁,也就是所谓的synchronzied关键字,它们两者的区别呢在于使用范围,synchronzied ...

  2. postgresql 并发访问_PostgreSQL并发控制(显式锁定)

    基于PostgreSQL 9.4 四.显式锁定 PostgreSQL提供了多种锁模式用于控制对表中数据的并发访问.这些模式可以用于在MVCC无法给出期望行为的场合.同样,大多数PostgreSQL命令 ...

  3. 连续锁定2个不同的锁会死锁_研究死锁–第5部分:使用显式锁定

    连续锁定2个不同的锁会死锁 在我的上一个博客中,我研究了使用Java的传统synchronized关键字和锁排序来修复破碎的,死锁的余额转移示例代码. 但是,有另一种方法称为显式锁定. 在这里,将锁定 ...

  4. 研究死锁–第5部分:使用显式锁定

    在我的上一个博客中,我研究了使用Java的传统synchronized关键字和锁排序来修复破碎的,死锁的余额转移示例代码. 但是,有一种替代方法称为显式锁定. 这里,将锁定机制称为显式而非隐式的想法是 ...

  5. [MySQL] mysql 的行级显式锁定和悲观锁

    [MySQL] mysql 的行级显式锁定和悲观锁 隐式和显式锁定: 1.innodb是两阶段锁定协议,隐式锁定比如在事务的执行过程中.会进行锁定,锁只有在commit或rollback的时候,才会同 ...

  6. Java并发教程–重入锁

    Java的synced关键字是一个很棒的工具–它使我们能够以一种简单可靠的方式来同步对关键部分的访问,而且也不难理解. 但是有时我们需要对同步进行更多控制. 我们要么需要分别控制访问类型(读取和写入) ...

  7. Java并发教程–可调用,将来

    从Java的第一个发行版开始,Java的美丽之处之一就是我们可以轻松编写多线程程序并将异步处理引入我们的设计中. Thread类和Runnable接口与Java的内存管理模型结合使用,意味着可以进行简 ...

  8. Java并发教程–信号量

    这是我们将要进行的Java并发系列的第一部分. 具体来说,我们将深入探讨Java 1.5及更高版本中内置的并发工具. 我们假设您对同步和易失性关键字有基本的了解. 第一篇文章将介绍信号量-特别是对信号 ...

  9. Java并发教程–线程池

    Java 1.5中提供的最通用的并发增强功能之一是引入了可自定义的线程池. 这些线程池使您可以对诸如线程数,线程重用,调度和线程构造之类的东西进行大量控制. 让我们回顾一下. 首先,线程池. 让我们直 ...

  10. Java并发教程– CountDownLatch

    Java中的某些并发实用程序自然会比其他并发实用程序受到更多关注,因为它们可以解决通用问题而不是更具体的问题. 我们大多数人经常遇到执行程序服务和并发集合之类的事情. 其他实用程序不太常见,因此有时它 ...

最新文章

  1. 网络攻城狮怎么看待TCP/IP协议与UDP协议?
  2. 在CentOS6.8下安装Docker
  3. MLCC噪声啸叫及对策
  4. tensorflow+numpy 深度学习相关函数(持续更新)
  5. Android之Handler探索
  6. OpenGL 2D Prefix Sum 2维前缀总和的实例
  7. 万网空间的数据库配置方法
  8. less最后一页 linux_必备linux命令有哪些?你了解多少
  9. WebRTC NAT穿透服务器 coturn服务搭建
  10. 海龟编辑器 html版,海龟编辑器官方版
  11. C# 短视频 无水印解析 原视频下载(超详细)
  12. 项目验收文档模板(四)
  13. linux nginx rtmp 直播,nginx+rtmp简单直播
  14. 尚硅谷nginx学习
  15. Day739.GEO经纬度数据结构自定义数据结构 -Redis 核心技术与实战
  16. 教你如何做好微信营销说到微信营销
  17. 【每日早报】2019/08/08
  18. 10.Quartz 常用配置
  19. 用while和for循环分别计算100以内奇数和偶数的和,并输出。(Java)
  20. 《Linux命令行与shell脚本编程大全》第三章

热门文章

  1. 第五章循环结构(一)
  2. double类型进行比较排序
  3. BigDecimal保留两位小数,不足两位补0
  4. rabbitmq生产者基于事务实现发送确认
  5. 转:认识cpu、核与线程
  6. ReviewForJob——堆排序
  7. Java SE 知识点
  8. Mybatis3(3)动态 SQL
  9. azure blob_使用Azure Blob存储托管Maven工件
  10. swagger生成示例_生成器设计模式示例