一、显式锁简介

   显式锁,这个叫法是相对于隐式锁synchronized而言的,加锁和解锁都要用户显式地控制。显示锁Lock是在Java5中添加到jdk的,同synchronized一样,这也是一种协调共享对象访问的机制。但是它不是用来替代内置锁的,而是一种可选择的高级功能。

1、Lock接口提供了synchronized关键字不具备的主要特性:

  • 尝试非阻塞获取锁:当前线程尝试获取锁,如果这一时刻,锁没有被其他线程占有,那么成功获取锁并返回。
  • 能被中断地获取锁:当线程正在等待获取锁,则这个线程能够 响应中断,即当中断来了,线程不会阻塞等待获取锁,抛出中断异常。
  • 超时获取锁:在指定的截止时间前获取锁,如果截止时间到了仍旧无法获取锁,则返回;

关于Lock与synchronized的区别,请参考我的上一篇博文。

2、两种显式锁

JDK中提供了两种显式锁,即Lock的实现方式有两种:ReentrentLock(重入锁)、ReentrantReadWriteLock.ReadLock 和 ReentrantReadWriteLock.WriteLock(这两个锁是由其父类ReentrantReadWriteLock 控制使用,可视为一体,称为读写锁)。具体的Lock接口的继承结构,可参考下图:


二、Lock接口的API

方法名称 描述
void lock( ) 阻塞地获取锁,直到获取到锁才返回,而且是不可中断的。
void lockInterruptibly( ) throws InterruptedException 可中断地获取锁,与lock()方法的不同之处,在于该方法在阻塞等待锁的过程中会 响应中断。
boolean tryLock( ) 尝试非阻塞地获取锁,即调用该方法后,立刻返回,成功获取锁,返回true,失败则返回false。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException 可中断的超时获取锁,在以下3种情况下会返回:
1. 当前线程在指定时间内获得了锁;
2. 当前线程在指定时间内被中断;
3. 指定时间结束(超时结束),返回false;
void unlock( ) 释放锁。
Condition newCondition() 等待通知组件,当前线程只有获得了锁,才能调用该组件的wait()方法,调用后,线程将会释放锁

三、ReentrentLock重入锁 详解

  ReentrentLock 是Lock接口的常用的实现类,是一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。更加灵活(实现了显示锁的特性)。下面将由ReentrentLock 来介绍体验显示锁的特性:

1、可中断锁与不可中断锁 - - lockInterruptibly( )、lock( )

  Lock接口不仅提供了不可中断锁(synchronized是不可中断的),还有可中断锁。在某些应用场景下,可中断锁的用处很大:当检测到线程等待锁的时间过长,不能继续等待,需要进行下一步操作;或者某个任务已经完成了,则中断其他等待锁来完成这个任务的线程。
   显式锁的加锁和解锁都是由用户来操作,所以用户一旦忘记释放锁了,很可能就会造成线程讥饿。正确的用是使用 try-finally 确保锁能被正确释放。

   public void m() { lock.lock();  // block until condition holdstry {// ... method body} finally {lock.unlock()}}

如果是可中断方式获取锁 lockInterruptibly,则要 try-finally 要处于 捕获中断异常的 try-catch 块间,或者在方法上抛出中断异常。

public void method() throws InterruptedException {//抛出中断异常lock.lockInterruptibly();try {  //方法体.....}finally {lock.unlock();}
}
   public void m() { try {lock.lockInterruptibly();try{// ... method body}finally{lock.unlock();}} catch (InterruptedException e) {e.printStackTrace();}}

中断锁的例子:

//静态变量
static Lock lock =new ReentrantLock();public static void main(String[] args) {Thread A = new Thread("A"){@Overridepublic void run() {//不可中断锁,在等待获取锁的过程,忽略中断lock.lock();try {System.out.println("线程"+getName()+"成功获取锁");} finally {lock.unlock();}}};Thread B = new Thread("B"){@Overridepublic void run() {try {//可中断锁,在等待获取锁的过程中,如果有中断到来,将会停止获取锁,并抛出中断异常lock.lockInterruptibly();try{//System.out.println("线程"+getName()+"成功获取锁");}finally{lock.unlock();}} catch (InterruptedException e) {e.printStackTrace();}}};//mian线程保持着锁时,再启动A、B线程,确保中断A、B线程时,A、B线程在等待获取锁lock.lock();try{A.start();B.start();System.out.println("中断A、B线程");A.interrupt();B.interrupt();}finally{lock.unlock();}}

运行结果:


2、非阻塞获取锁 - - tryLock( )

调用此方法后,无论是否成功获取锁,都将立刻返回,成功获取锁,返回true,否则,返回false;

public static void main(String[] args) throws InterruptedException {Lock lock = new ReentrantLock();Thread A = new Thread("A"){@Overridepublic void run() {if(lock.tryLock()){//尝试非阻塞获取锁try{System.out.println(getName()+"成功获取锁");}finally {//释放锁lock.unlock();}}else{System.out.println(getName()+"获取锁失败!");}}};if(lock.tryLock()){//main线程成功获取锁后,启动线程Atry{A.start();System.out.println(Thread.currentThread().getName()+"启动线程A");//sleep可以保持锁,模拟main线程还要运行1秒TimeUnit.SECONDS.sleep(1);}finally {lock.unlock();}}else{System.out.println("程序结束!");}
}

运行结果:

main启动线程A
A获取锁失败!


3、超时获取锁 - - tryLock(long time, TimeUnit unit)

与tryLock( )相比,除了不是立刻返回,而是超时等待外,tryLock(long time, TimeUnit unit)还是可以被中断的。
改造一下上面的例子,将tryLock()换成 tryLock(1,TimeUnit.SECONDS):

public static void main(String[] args) throws InterruptedException {Lock lock = new ReentrantLock();Thread A = new Thread("A"){@Overridepublic void run() {try {if(lock.tryLock(1,TimeUnit.SECONDS)){//超时等待获取锁try{System.out.println(getName()+"成功获取锁");}finally {lock.unlock();}}else{System.out.println(getName()+"获取锁失败!");}} catch (InterruptedException e) {e.printStackTrace();}}};if(lock.tryLock()){//main线程成功获取锁后,启动线程Atry{A.start();System.out.println(Thread.currentThread().getName()+"启动线程A");//sleep可以保持锁,模拟main线程还要运行1秒TimeUnit.SECONDS.sleep(1);}finally {lock.unlock();}}else{System.out.println("程序结束!");}
}

运行结果:

main启动线程A
A成功获取锁

上面的3小点是 显式锁的区别与隐式锁 synchronize的特性,接下来的几点,则是ReentrantLock的方法讲解,不是 显式锁的特性;


4、可重入的锁

可重入的锁:是指线程持有了某个锁,便可以进入任意的该锁同步着的代码块。
不可重入的锁:线程进入任何一个同步的代码块都必须获取锁,即使这些代码块是同一个锁;
  使用可重入锁,可以很大程度地避免死锁,所以不可重入锁的应用场景很少,JDK提供的锁(synchronize、ReentrentLock、ReentrantReadWriteLock)都是可重入的锁。当线程获取重入锁时,先判断线程是不是已经持有该锁,如果是,那么重入计数器加一,否则去获取该锁。ReentrentLock中,提供了锁被线程重入的次数的方法 - - getHoldCount()。

//静态变量
static ReentrantLock lock = new ReentrantLock();
static int num = 5;
public static void main(String[] args)Thread B = new Thread("B"){@Overridepublic void run() {lock.lock();try{int aa = 5*5;//countNumber里面也要获取同步锁,而且与当前线程所拥有的锁是同一个countNumber(aa);  //可重入,意味着不需要再次去等待获取锁System.out.println("num的值是:"+num);}finally {lock.unlock();}}};B.start();
}public static void countNumber(int a){ //包含同步代码块lock.lock(); //如果是重入,则重入计数器加一try{num+=a;System.out.println("锁lock被当前线程重入的次数:"+lock.getHoldCount());}finally {lock.unlock();//如果是重入,则重入计数器减一}
}

运行结果:

锁lock被当前线程重入的次数:2
num的值是:30


5、公平锁 和 非公平锁

ReentrantLock 支持公平锁,可以在构造方法中传入参数设置,默认为非公平锁;
ReentrantLock(boolean fair): 创建一个具有给定公平策略的 ReentrantLock。此类的构造方法接受一个可选的公平 参数。当设置为 true 时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程,即先来先获取锁。否则此锁将无法保证任何特定访问顺序。
关于公平锁的注意以下三点:

  • 与采用默认设置(使用不公平锁)相比,使用公平锁的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢),但是在获得锁和保证锁分配的均衡性时差异较小。
  • 公平锁不能保证线程调度的公平性。公平锁保证的是获取锁的顺序。
  • 未定时的 tryLock 方法并没有使用公平设置。因为即使其他线程正在等待,只要该锁是可用的,此方法就可以获得成功。
public class MyTest{//设置成公平锁模式static ReentrantLock lock = new ReentrantLock(true);public static void main(String[] args) {Thread threadA = new Thread(new MyRunable4(),"threadA"); Thread threadB = new Thread(new MyRunable4(),"threadB"); Thread threadC = new Thread(new MyRunable4(),"threadC");Thread threadD = new Thread(new MyRunable4(),"threadD");//启动四个线程threadA.start();threadB.start();threadC.start();threadD.start();}public static void countNumber(){//lock.lock();try{System.out.println("线程锁"+Thread.currentThread().currentThread().getName()+"成功获取了锁!");}finally{lock.unlock();}   }}
class MyRunable4 implements Runnable{@Overridepublic void run() {while(true){MyTest.countNumber();}}
}

运行结果:
  下面的结果尽管不是按照启动的顺序来执行(这是因为调用start( )方法后是进入就绪队列,公平锁无法保证线程的调度,因此4个线程谁被先调度就先去获取锁),但是却是一直按照特定的顺序来执行的(C->B->A->D);

线程锁threadC成功获取了锁!
线程锁threadB成功获取了锁!
线程锁threadA成功获取了锁!
线程锁threadD成功获取了锁!
线程锁threadC成功获取了锁!
线程锁threadB成功获取了锁!
线程锁threadA成功获取了锁!
线程锁threadD成功获取了锁!
线程锁threadC成功获取了锁!
线程锁threadB成功获取了锁!
线程锁threadA成功获取了锁!
线程锁threadD成功获取了锁!
线程锁threadC成功获取了锁!
线程锁threadB成功获取了锁!
线程锁threadA成功获取了锁!
.......


6、ReentrentLock的其他API方法

ReentrentLock 除了实现Lock接口外,还提供了对检测和监视可能很有用的方法,包括三个protected方法。

boolean hasQueuedThread(Thread thread):
查询给定线程是否正在等待获取此锁。注意,因为随时可能发生取消,所以返回 true 并不保证此线程将获取此锁。此方法主要用于监视系统状态。
boolean hasQueuedThreads( ):
查询是否有些线程正在等待获取此锁。注意,因为随时可能发生取消,所以返回 true 并不保证有其他线程将获取此锁。此方法主要用于监视系统状态。
boolean hasWaiters(Condition condition):
查询是否有些线程正在等待与此锁有关的给定条件。注意,因为随时可能发生超时和中断,所以返回 true 并不保证将来某个 signal 将唤醒线程。此方法主要用于监视系统状态。
boolean isLocked( ):
查询此锁是否由任意线程保持。此方法用于监视系统状态,不用于同步控制。
boolean isFair( ): 如果此锁的公平设置为 true,则返回 true。
boolean isHeldByCurrentThread( ): 查询当前线程是否保持此锁。

int getHoldCount( ): 查询当前线程保持此锁的次数。
int getQueueLength( ):
返回正等待获取此锁的线程估计数。该值仅是估计的数字,因为在此方法遍历内部数据结构的同时,线程的数目可能动态地变化。此方法用于监视系统状态,不用于同步控制。
int getWaitQueueLength(Condition condition):
返回等待与此锁相关的给定条件的线程估计数。注意,因为随时可能发生超时和中断,所以只能将估计值作为实际等待线程数的上边界。此方法用于监视系统状态,不用于同步控制。

三个protected方法,提供给用户在继承ReentrentLock时,拥有更多的监控方法:

protected Thread getOwner( ):
返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null。当此方法被不是拥有者的线程调用,返回值反映当前锁状态的最大近似值。例如,拥有者可以暂时为 null,也就是说有些线程试图获取该锁,但还没有实现。此方法用于加快子类的构造速度,提供更多的锁监视设施。
protected Collection getQueuedThreads( ):
返回一个 collection,它包含可能正等待获取此锁的线程。因为在构造此结果的同时实际的线程 set 可能动态地变化,所以返回的 collection 仅是尽力的估计值。所返回 collection 中的元素没有特定的顺序。此方法用于加快子类的构造速度,以提供更多的监视设施。
protected Collection getWaitingThreads(Condition condition):
返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程。因为在构造此结果的同时实际的线程 set 可能动态地变化,所以返回 collection 的元素只是尽力的估计值。所返回 collection 中的元素没有特定的顺序。此方法用于加快子类的构造速度,提供更多的条件监视设施。

重点介绍以下两个方法:
1、boolean isHeldByCurrentThread( ): 查询当前线程是否保持此锁。
与内置监视器锁的 Thread.holdsLock(java.lang.Object) 方法类似,此方法通常用于调试和测试。例如,只在保持某个锁时才应调用的方法可以声明如下:

class X {ReentrantLock lock = new ReentrantLock();// ...public void m() { //在保持某个锁的条件下才进入,assert lock.isHeldByCurrentThread();// ... method body}}还可以用此方法来确保某个重入锁是否以非重入方式使用的,例如: class X {ReentrantLock lock = new ReentrantLock();// ...public void m() { assert !lock.isHeldByCurrentThread();lock.lock();try {// ... method body} finally {lock.unlock();}}}

2、public int getHoldCount( ):查询当前线程保持此锁的次数。
对于与解除锁操作不匹配的每个锁操作,线程都会保持一个锁。

保持计数信息通常只用于测试和调试。例如,如果不应该使用已经保持的锁进入代码的某一部分,则可以声明如下:

 class X {ReentrantLock lock = new ReentrantLock();// ...     public void m() { assert lock.getHoldCount() == 0;lock.lock();try {// ... method body} finally {lock.unlock();}}}

转载于:https://www.cnblogs.com/jinggod/p/8491242.html

显式锁(二)Lock接口与显示锁介绍相关推荐

  1. python多线程之线程锁(Lock)和递归锁(RLock)实例

    一.线程锁 Threading模块为我们提供了一个类,Threading.Lock锁.我们创建一个该类对象,在线程函数执行前,"抢占"该锁,执行完成后,"释放" ...

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

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

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

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

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

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

  5. 基类显式继承接口,类继承基类时又继承同一接口,引发接口方法混乱(显式继承接口的弊端)...

    基类BaseOutput显式继承了一个接口IOutput,之后类TrackOutput继承BaseOutput,同时又继承了IOutput接口,假定IOutput有方法Output,这样在TrackO ...

  6. 1、Lock接口以及ReentrantLock可重入锁

    1.序 文章目录 1.序 2.Lock 接口 3.AbstractQueuedSynchronizer 3.1 双端队列 3.2 state变量 4.ReentrantLock简介以及其非公平锁模式 ...

  7. 再也不用担心面试官让我用Redis实现分布式锁啦(二、Redisson实现分布式锁)

    目录 一.Jedis实现分布式锁 二.Redisson实现分布式锁(单机Redis) 一.引入依赖(3.5.7) 二.配置redis 三.配置RedisonConfig 四.提供锁接口及实现,方便统一 ...

  8. java lock接口_Java Lock接口

    Java Lock接口 java.util.concurrent.locks.Lock接口用作线程同步机制,类似于同步块.新的锁定机制更灵活,提供比同步块更多的选项. 锁和同步块之间的主要区别如下: ...

  9. java 7 锁优化_自Java 6/Java 7开始,Java虚拟机对内部锁的实现进行了一些优化。这些优化主要包括锁消除(Lock Elision)、锁粗化(Lock Coarse...

    自Java 6/Java 7开始,Java虚拟机对内部锁的实现进行了一些优化.这些优化主要包括锁消除(Lock Elision).锁粗化(Lock Coarsening).偏向锁(Biased Loc ...

最新文章

  1. python booleans_Python 2.1 BooleansComparisons
  2. dubbo图形化界面搭建_使用 JMeter 进行 Dubbo 性能测试
  3. C. Code a Trie(Trie+dfs+贪心)
  4. 图(关系网络)数据分析及阿里应用
  5. 像狗皮膏药一样的图片拖拉操作
  6. Java实现视频网站的视频上传、视频转码、视频关键帧抽图, 及视频播放功能
  7. 洛谷P1216 [USACO1.5][IOI1994]数字三角形 Number Triangles
  8. 将应用制作成镜像发布到服务器k8s上作为容器微服务运行。
  9. java 关于时间处理
  10. JAVA集合系列(1):集合的整体框架
  11. Linux学习笔记之CentOS7的 wheel组
  12. jquery datatable 前端分页和后端分页例子
  13. html调用js的方法
  14. 社区项目发现的问题四 datatable的注意事项
  15. 如何开启红米手机4X的ROOT超级权限
  16. Windows10 插入耳机没声音,安装驱动无效问题解决
  17. 计算机基础应用期末卷,计算机应用基础期末试卷(B卷)
  18. 柳絮纷飞的日子——CSS笔记之四
  19. 19 Python __dict__与dir()区别
  20. 如何在黑苹果上开启HiDPI分辨率

热门文章

  1. Python 面向对象程序设计(一)
  2. 刚进职场的IT前端小白,如何规划自己的未来之路?有哪些发展方向?
  3. cmd查看所有数据库 db2_db2 cmd命令操作
  4. 计算机网络自上而下影印版_《计算机网络(影印版)》 影印 【正版电子纸书阅读_PDF下载】- 书问...
  5. python3导入模块原理_python模块导入原理
  6. 水冒泡了几度_冒泡和选择排序详解
  7. linux脚本怎么获取参数,在Bash shell脚本编程中,如何正确无误获取到“脚本选项参数”和“脚本参数”呢?...
  8. Linux 环境下NFS服务安装及配置
  9. 值得收藏的Redis基础总结
  10. CentOS x86_64系统手动释放内存