Java提供一系列的显示锁类,均位于java.util.concurrent.locks包中。

锁的分类: 排他锁,共享锁

  • 排他锁又被称为独占锁,即读写互斥、写写互斥、读读互斥。
  • Java的ReadWriteLock是一种共享锁,提供读读共享,但读写和写写仍然互斥。

Lock接口

Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。

  1. public interface Lock {
  2. void lock();
  3. void lockInterruptibly() throws InterruptedException;
  4. boolean tryLock();
  5. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  6. void unlock();
  7. Condition newCondition();
  8. }

Lock API详解

void lock();

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

void lockInterruptibly() throws InterruptedException;

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

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

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

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

如果当前线程在下列情况会抛出 InterruptedException,并清除当前线程的已中断状态:

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

Condition newCondition();

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

boolean tryLock();

仅在调用时锁为空闲状态才获取该锁。通常对于那些不是必须获取锁的操作可能有用。

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

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

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

1.如果锁可用,则此方法将立即返回值 true。

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

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

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

4.如果当前线程:

  • 在进入此方法时已经设置了该线程的中断状态;或者
  • 在获取锁时被中断,并且支持对锁获取的中断,则将抛出 InterruptedException,并会清除当前线程的已中断状态。

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

void unlock();

释放锁。对应于lock()、tryLock()、tryLock(xx)、lockInterruptibly()等操作,如果成功的话应该对应着一个unlock(),这样可以避免死锁或者资源浪费。

Lock的使用

Lock的lock()方法保证了只有一个线程能够执有此锁。对于任何一个lock()方法,都需要一个unlock()方法与之对应,通常情况下为了保证unlock()方法总是能够执行,unlock()方法被置于finally中。

Lock的标准适用方法如下,等价于synchronized代码块:

  1. lock.lock();
  2. try {
  3. //...需要保证线程安全的代码。
  4. } finally {
  5. lock.unlock();
  6. }

ReentrantLock

Reentrant英文意思为重入。ReentrantLock即重入锁。

ReentrantLock提供了与synchronized相同的互斥和内存可见性的保证。获得ReentrantLock的锁与进入synchronized块有着相同的内存语义,释放ReentrantLock锁与退出synchronized块有相同的内存语义。

那么为什么要创建与synchronized如此相似的东西呢?原因是synchronized在大部分情况下能够很好的工作,但是有些功能上存在着局限:

  1. 内部锁不能中断那些正在等待获取锁的进程,并且在请求锁失败的情况下,线程必须无限等待;
  2. 内部锁必须在获取它们的代码块中被释放;这很好的简化了代码,但是在某些情况下,一个更灵活的加锁机制提供了更好的活跃度和性能。

反之ReentrantLock拥有如下优点:

  • ReentrantLock可以轮询和可定时的锁请求。lock.tryLock()
  • ReentrantLock可中断的锁获取操作。lock.lockInterruptibly()
  • jdk6之前,ReentrantLock性能优于synchronized。JDK6开始,两者性能差不多。

相对于synchronized来说,synchronized的锁的获取是释放必须在一个模块里,获取和释放的顺序必须相反,而Lock则可以在不同范围内获取释放,并且顺序无关。

ReentrantLock的特性

可重入

synchronized和ReentrantLock均有可重入性。

在使用synchronized时,一个线程请求得到一个对象锁后再次请求此对象锁,可以再次得到该对象锁。即当一个线程已经进入到synchronized方法/块中时,可以进入到本类的其他synchronized方法/块中。

对于ReentrantLock,单线程可以重复获取锁,同时一定要重复释放。

例:两次获取锁,则必须两次解锁。

  1. ReentrantLock lock = new ReentrantLock();
  2.  
  3. lock.lock();
  4. lock.lock();
  5. try{
  6. // ...
  7. }finally{
  8. lock.unlock();
  9. lock.unlock();
  10. }

可中断

ReentrantLock.lockInterruptibly() 该方法与lock()方法类似,但该方法用于可中断的加锁。

在lockInterruptibly() 锁定的代码中,一旦接收到中断通知,就会抛出InterruptedException异常。

所以在被锁定的代码中ReentrantLock可以响应其他线程发起的中断通知。而被synchronized加锁的代码中,无法获取中断通知。

可限时

超时不能获得锁,就返回false,不会永久等待构成死锁。

ReentrantLock.tryLock()方法用于尝试锁定。参数为等待时间。该方法返回boolean值。若锁定成功,则返回true。锁定失败,则返回false。

公平锁

公平锁不会产生线程饥饿,锁被线程先来先得。ReentrantLock内部需要维护一个线程队列,性能稍高,如无必要,没必要使用。

public ReentrantLock(boolean fair) 是一个构造方法,fair默认为false,当设置为true时,及表示当前构造的锁是公平锁。

例:创建一个公平锁。

  1. public static ReentrantLock fairLock = new ReentrantLock(true);

《Java并发编程实践》中说明:当需要可定时的、可轮询的与可中断的锁获取操作,公平队列,或者非块结构的锁,建议使用ReentrantLock。否则,请使用synchronized。

例:使用Lock定义一个类似于AtomicInteger的原子操作的整数类。

  1. public class AtomicIntegerWithLock {
  2. private int value;
  3. private Lock lock = new ReentrantLock();
  4.  
  5. public AtomicIntegerWithLock() {
  6. super();
  7. }
  8.  
  9. public AtomicIntegerWithLock(int value) {
  10. this.value = value;
  11. }
  12.  
  13. public final int get() {
  14. lock.lock();
  15. try {
  16. return value;
  17. } finally {
  18. lock.unlock();
  19. }
  20. }
  21.  
  22. public final void set(int newValue) {
  23. lock.lock();
  24. try {
  25. value = newValue;
  26. } finally {
  27. lock.unlock();
  28. }
  29. }
  30.  
  31. public final int getAndSet(int newValue) {
  32. lock.lock();
  33. try {
  34. int ret = value;
  35. value = newValue;
  36. return ret;
  37. } finally {
  38. lock.unlock();
  39. }
  40. }
  41.  
  42. public final boolean compareAndSet(int expect, int update) {
  43. lock.lock();
  44. try {
  45. if (value == expect) {
  46. value = update;
  47. return true;
  48. }
  49. return false;
  50. } finally {
  51. lock.unlock();
  52. }
  53. }
  54.  
  55. public final int getAndIncrement() {
  56. lock.lock();
  57. try {
  58. return value++;
  59. } finally {
  60. lock.unlock();
  61. }
  62. }
  63.  
  64. public final int getAndDecrement() {
  65. lock.lock();
  66. try {
  67. return value--;
  68. } finally {
  69. lock.unlock();
  70. }
  71. }
  72.  
  73. public final int incrementAndGet() {
  74. lock.lock();
  75. try {
  76. return ++value;
  77. } finally {
  78. lock.unlock();
  79. }
  80. }
  81.  
  82. public final int decrementAndGet() {
  83. lock.lock();
  84. try {
  85. return --value;
  86. } finally {
  87. lock.unlock();
  88. }
  89. }
  90.  
  91. public String toString() {
  92. return Integer.toString(get());
  93. }
  94.  
  95. }

ReadWriteLock 读写锁

读写锁:可以被多个读者访问或者被一个写者访问。读写锁提供读写分离功能。

  1. public interface ReadWriteLock {
  2. Lock readLock();
  3. Lock writeLock();
  4. }

特性:

  • 读-读不互斥:读读之间不阻塞。
  • 读-写互斥:读阻塞写,写也会阻塞读。
  • 写-写互斥:写写阻塞。

ReadWriteLock最大的特性就是读读共享(不互斥),例如A线程读锁正在进行读取操作,此时如果B线程请求读锁,那么B线程可以马上顺利获得读锁而无需等待,但此时如果C线程请求写锁,那么C线程需要等待锁可用。

ReadWriteLock由于提供了读读共享而增加了复杂性,所以在读写都相当频繁的场景并不能体现出性能优势,只有在读操作极多而写操作极少的场景下才能体现其性能优势。比如,一个应用系统安装完成后需要导入一批维护性的初始化数据,这些数据可以通过界面修改,但需要修改的情况极少,当系统一启动就会自动加载初始化数据到指定数据结构(如HashMap)供各个模块读取使用,那么可以为这些数据的读写加ReadWriteLock,以提高读取性能并保持数据的一致性。

ReentrantReadWriteLock 实现类

ReentrantReadWriteLock类是ReadWriteLock接口的一个实现,它与ReentrantLock类一样提供了公平竞争与不公平竞争两种机制,默认也是使用非公平竞争机制。ReentrantLock是排他锁,使用非公平竞争机制时,抢占的机会相对还是比较少的,只有当新请求恰逢锁释放时才有机会抢占,所以发生线程饥饿的现象几乎很少。然而ReentrantReadWriteLock是共享锁,或者说读读共享,并且经常使用于读多写少的场景,即请求读操作的线程多而频繁而请求写操作的线程极少且间隔长,在这种场景下,使用非公平竞争机制极有可能造成写线程饥饿。比如,R1线程此时持有读锁且在进行读取操作,W1线程请求写锁所以需要排队等候,在R1释放锁之前,如果R2,R3,...,Rn 不断的到来请求读锁,因为读读共享,所以他们不用等待马上可以获得锁,如此下去W1永远无法获得写锁,一直处于饥饿状态。所以使用ReentrantReadWriteLock类时,小心选择公平机制,以免遇到出乎预料的结果。

最后,Java5的读写锁实现有瑕疵,可能发生死锁,在Java6已经修复,所以避免使用Java5读写锁。

例:创建读锁与写锁。

  1. private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
  2. private static Lock readLock = readWriteLock.readLock();
  3. private static Lock writeLock = readWriteLock.writeLock();

例:读写锁的经典应用,实现一个简单的缓存

  1. import java.util.HashMap;
  2. import java.util.Map;
  3. import java.util.concurrent.locks.ReadWriteLock;
  4. import java.util.concurrent.locks.ReentrantReadWriteLock;
  5.  
  6. /**
  7. * 用读写锁实现的一个缓存系统,读的时候可以并发执行,当缓存中没有数据时,要到数据库中查询数据.
  8. * 此时只能写数据,不能读数据。当完数据之后,又可以并发地读取数据。这样做的话,可以提高系统的效率.
  9. */
  10. public class MyCacheSystem {
  11.  
  12. // 定义一个map用来存放要缓存起来的数据
  13. private Map<String, Object> cache = new HashMap<String, Object>();
  14.  
  15. private ReadWriteLock rwl = new ReentrantReadWriteLock();
  16.  
  17. // 该方法中,读数据可以并发地读取,写数据与读数据,写数据与写数据之间不能并发地运行
  18. public Object getData(String key) {
  19. // 刚进来的时候,上一把写锁
  20. rwl.readLock().lock();
  21. Object obj = null;
  22. try {
  23. obj = cache.get(key);
  24. if (obj == null) {
  25. // 如果数据为空,则需要到数据库中查询数据,所以这时候把读锁释放掉,上一把写锁,不能同时写数据
  26. // 在上写锁之前,首先要把读锁释放掉
  27. rwl.readLock().unlock();
  28. rwl.writeLock().lock();
  29. // 查询数据库的代码
  30. try {
  31. // 必须重新检查obj是否为空,因为这时候,另外一个线程可能会获得写锁,从而让obj有值
  32. if (obj == null) {
  33. obj = "查询数据库得到的数据";
  34. }
  35. } finally {
  36. rwl.writeLock().unlock();
  37. }
  38.  
  39. // 因为前面释放了写锁,所以这里要把写锁重新锁上
  40. rwl.readLock().lock();
  41. }
  42. } finally {
  43. rwl.readLock().unlock();
  44. }
  45.  
  46. return obj;
  47. }
  48.  
  49. }

关于读写锁的一些知识:

1.重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。

2.WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有。反过来ReadLock想要升级为WriteLock则不可能。

3.ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥.这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。

4.不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。

5.WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常。

Java并发——显示锁的更多相关文章

  1. java 并发多线程 锁的分类概念介绍 多线程下篇(二)

    接下来对锁的概念再次进行深入的介绍 之前反复的提到锁,通常的理解就是,锁---互斥---同步---阻塞 其实这是常用的独占锁(排它锁)的概念,也是一种简单粗暴的解决方案 抗战电影中,经常出现为了阻止日 ...

  2. java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock

    原文:java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock 锁 锁是用来控制多个线程访问共享资源的方式,java中可以使用synch ...

  3. Java并发 - (无锁)篇6

    , 摘录自葛一鸣与郭超的 [Java高并发程序设计]. 本文主要介绍了死锁的概念与一些相关的基础类, 摘录自葛一鸣与郭超的 [Java高并发程序设计]. 无锁是一种乐观的策略, 它假设对资源的访问是没 ...

  4. 从源码学习Java并发的锁是怎么维护内部线程队列的

    从源码学习Java并发的锁是怎么维护内部线程队列的 在上一篇文章中,凯哥对同步组件基础框架- AbstractQueuedSynchronizer(AQS)做了大概的介绍.我们知道AQS能够通过内置的 ...

  5. Java并发编程锁之独占公平锁与非公平锁比较

    Java并发编程锁之独占公平锁与非公平锁比较 公平锁和非公平锁理解: 在上一篇文章中,我们知道了非公平锁.其实Java中还存在着公平锁呢.公平二字怎么理解呢?和我们现实理解是一样的.大家去排队本着先来 ...

  6. Java并发编程锁系列之ReentrantLock对象总结

    Java并发编程锁系列之ReentrantLock对象总结 在Java并发编程中,根据不同维度来区分锁的话,锁可以分为十五种.ReentranckLock就是其中的多个分类. 本文主要内容:重入锁理解 ...

  7. java 并发(六) --- 锁

          阅读前阅读以下参考资料,文章图片或代码部分来自与参考资料 概览 一张图了解一下java锁. 注 : 阻塞将会切换线程,切换内核态和用户态,是比较大的性能开销 各种锁 为什么要设置锁的等级 ...

  8. 【Java并发】锁机制

    一.重入锁 二.读写锁 三.悲观锁.乐观锁 3.1 悲观锁 3.2 乐观锁 3.3 CAS操作方式 3.4 CAS算法理解 3.5 CAS(乐观锁算法) 3.6 CAS缺点 四.原子类 4.1 概述 ...

  9. java 并发线程锁

     1.同步和异步的区别和联系 异步,执行完函数或方法后,不必阻塞性地等待返回值或消息,只需要向系统委托一个异步过程,那么当系统接收到返回 值或消息时,系统会自动触发委托的异步过程,从而完成一个完整的流 ...

随机推荐

  1. Cannot proxy target class because CGLIB2 is not available. Add CGLIB to the class path or specify proxy interfaces

    问题解决:缺少jar包 cglib-2.1.3.jar

  2. phpMyAdmin import.php 跨站脚本漏洞

    漏洞名称: phpMyAdmin import.php 跨站脚本漏洞 CNNVD编号: CNNVD-201402-281 发布时间: 2014-02-21 更新时间: 2014-02-21 危害等级: ...

  3. ECC校验优化之路

    引子: 今天上嵌入式课程时,老师讲到Linux的文件系统,讲的重点是Linux中对于nand flash的ECC校验和纠错.上课很认真地听完,确实叹服代码作者的水平. 晚上特地下载了Linux最新的内 ...

  4. 【转】关于usr/bin/ld: cannot find -lxxx问题总结

    原文网址:http://eminzhang.blog.51cto.com/5292425/1285705 /usr/bin/ld: cannot find -lxxx问题总结   linux下编译应用 ...

  5. SharePoint 2010 PowerShell 系列 之 备份、还原、部署 .WSP

    转:http://www.cnblogs.com/Fengger/archive/2012/08/24/2654093.html PowerShell系列目录 最近在部署测试环境,就顺便把PowerS ...

  6. STL find() ,还是挺重要的

    template<class InputIterator, class T> InputIterator find (InputIterator first, InputIterator ...

  7. zzzz

    using System; using System.Collections.Generic; using System.Diagnostics; using System.Management; u ...

  8. 【译】Selenium 2.0 WebDriver

    Selenium WebDriver   注意:我们正致力于完善帮助指南的每一个章节,虽然这个章节仍然存在需要完善的地方,不过我们坚信当前你看到的帮助信息是精确无误的,后续我们会提供更多的指导信息来完 ...

  9. HW3.5

    import java.util.Scanner; public class Solution { public static void main(String[] args) { int n1 = ...

  10. pytho

    字符串格式化:求模操作符%可以用来将其他值转换为包含转换标志的字符串,对值进行不同方法的格式化,左右对齐,字段宽度精度,增加符号,左填充数字 字符串方法join split istitle capit ...