显式锁(二)Lock接口与显示锁介绍

时间:2022-12-30 20:06:54

一、显式锁简介

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

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

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

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

2、两种显式锁

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

显式锁(二)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 holds
try {
// ... 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"){ @Override
public void run() {
//不可中断锁,在等待获取锁的过程,忽略中断
lock.lock();
try {
System.out.println("线程"+getName()+"成功获取锁");
} finally {
lock.unlock();
}
}
}; Thread B = new Thread("B"){ @Override
public 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();
} }

运行结果:

显式锁(二)Lock接口与显示锁介绍


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

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

public static void main(String[] args) throws InterruptedException {

	Lock lock = new ReentrantLock();
Thread A = new Thread("A"){
@Override
public void run() {
if(lock.tryLock()){//尝试非阻塞获取锁
try{
System.out.println(getName()+"成功获取锁");
}finally {//释放锁
lock.unlock();
}
}else{
System.out.println(getName()+"获取锁失败!");
}
}
}; if(lock.tryLock()){//main线程成功获取锁后,启动线程A
try{
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"){
@Override
public 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线程成功获取锁后,启动线程A
try{
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"){
@Override
public 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{

	@Override
public 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();
}
}
}

显式锁(二)Lock接口与显示锁介绍的更多相关文章

  1. 编写高质量代码改善C#程序的157个建议——建议46:显式释放资源需继承接口IDisposable

    建议46:显式释放资源需继承接口IDisposable C#中的每一个类型都代表一种资源,资源分为两类: 托管资源:由CLR管理分配和释放的资源,即从CLR里new出来的对象. 非托管资源:不受CLR ...

  2. 【转】锁(lock)知识及锁应用

    sql server锁(lock)知识及锁应用转自:http://blog.csdn.net/huwei2003/article/details/4047191 关键词:锁提示,锁应用 提示:这里所摘 ...

  3. SQL SERVER锁(LOCK)知识及锁应用

    提示:这里所摘抄的关于锁的知识有的是不同sql server版本的,对应于特定版本时会有问题. 一 关于锁的基础知识 (一). 为什么要引入锁 当多个用户同时对数据库的并发操作时会带来以下数据不一致的 ...

  4. java中的锁之Lock接口与Condition接口

    一.Lock源码. 1.是一个接口.一共有6个方法. 2.方法详细如下: (1)当前线程尝试获取锁.结果分两种情况,一是成功获取到锁,则返回:二是获取锁失败,则一直等待.不响应中断请求. (2)当前线 ...

  5. 【java并发编程】十三章:显式锁:LOCK

    java5以后,新增了显式锁,用于当内置锁不能满足需求后可选择的一种高级方案. lock接口的特点 与内置锁一样,他能提供互斥性,内存可见性,可重入等特征,与内置锁不同的是,Lock提供了一种无条件, ...

  6. java里的锁总结(synchronized隐式锁、Lock显式锁、volatile、CAS)

    一.介绍 首先, java 的锁分为两类: 第一类是 synchronized 同步关键字,这个关键字属于隐式的锁,是 jvm 层面实现,使用的时候看不见: 第二类是在 jdk5 后增加的 Lock ...

  7. Java显式锁

    Java 显式锁. 一.显式锁 什么是显式锁? 由自己手动获取锁,然后手动释放的锁. 有了 synchronized(内置锁) 为什么还要 Lock(显示锁)? 使用 synchronized 关键字 ...

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

    隐式和显式锁定:1.innodb是两阶段锁定协议,隐式锁定比如在事务的执行过程中.会进行锁定,锁只有在commit或rollback的时候,才会同时被释放2.特定的语句进行显式锁定 select .. ...

  9. Java显式锁学习总结之一:概论

    我们都知道在java中,当多个线程需要并发访问共享资源时需要使用同步,我们经常使用的同步方式就是synchronized关键字,事实上,在jdk1.5之前,只有synchronized一种同步方式.而 ...

随机推荐

  1. Entity Framework 使用Mysql的配置文件

    <?xml version="1.0" encoding="utf-8"?> <configuration> <configSec ...

  2. 自适应label的高度

    iOS7以下的系统可使用方法 //获得当前cell高度 CGRect frame = [self frame]; //文本赋值 self.introduction.text = text; //设置l ...

  3. 使用 libevent 和 libev 提高网络应用性能——I&sol;O模型演进变化史

    构建现代的服务器应用程序需要以某种方法同时接收数百.数千甚至数万个事件,无论它们是内部请求还是网络连接,都要有效地处理它们的操作. 有许多解决方案,但事件驱动也被广泛应用到网络编程中.并大规模部署在高 ...

  4. Android SDK离线安装方法详解&lpar;加速安装&rpar; 转

    AndroidSDK在国内下载一直很慢··有时候通宵都下不了一点点,最后只有选择离线安装,现在发出离线安装地址和方法,希望对大家有帮助! 离线安装包下载地址:http://dl.vmall.com/c ...

  5. Input

    Input Basic Input Old Input Files Please note that both Input and Request do NOT sanitize your data, ...

  6. Java 逆变与协变的名词说明

    最近在研究Thinking in Java的时候,感觉逆变与协变有点绕,特意整理一下,方便后人.我参考于Java中的逆变与协变,但是该作者整理的稍微有点过于概念化,我在这里简单的说一下 我对于协变于逆 ...

  7. 【Android】版本的名称

    http://www.cnblogs.com/imlucky/archive/2011/10/21/2220596.html

  8. Java&period;net&period;SocketException&colon; Unrecognized Windows Sockets error&colon; 0&colon; JVM&lowbar;Bind异常

    端口被占用,可能是其他程序占用,也有可能是自己代码逻辑不对,比如BZ在写SocketServer时把添加端口的代码放进了while里(sasasa.....). 查看本机端口是否被占用:netstat ...

  9. android ncnn

    1.下载解压ndk wget https://dl.google.com/android/repository/android-ndk-r17b-linux-x86_64.zip unzip andr ...

  10. &lbrack;都是原创&rsqb;Php simplexml 添加节点

    Php simplexml 添加节点 原始代码如下 //================<? //创建xml对象$xml = new SimpleXMLElement('<Messages ...