(六)Lock锁的使用


(六)Lock锁的使用_第1张图片

1.1 Lock接口简介

锁是用于通过多个线程控制对共享资源的访问的工具。通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如ReadWriteLock的读写锁。

在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的。JDK1.5之后并发包中新增了Lock接口以及相关实现类来实现锁功能。

虽然synchronized方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您需要以更灵活的方式处理锁。例如,用于遍历并发访问的数据结构的一些算法需要使用“手动”或“链锁定”:您获取节点A的锁定,然后获取节点B,然后释放A并获取C,然后释放B并获得D等。在这种场景中synchronized关键字就不那么容易实现了,使用Lock接口容易很多。

Lock接口的实现类: 

ReentrantLock , ReentrantReadWriteLock.ReadLock , ReentrantReadWriteLock.WriteLock

1.2 Lock的简单使用

(六)Lock锁的使用_第2张图片

因为Lock是接口所以使用时要结合它的实现类,另外在finall语句块中释放锁的目的是保证获取到锁之后,最终能够被释放

注意: 最好不要把获取锁的过程写在try语句块中,因为如果在获取锁时发生了异常,异常抛出的同时也会导致锁无法被释放

1.3 Lock接口的特性和常见方法

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

特性                            描述

尝试非阻塞地获取锁 :当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁

能被中断地获取锁获 :取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放

超时获取锁 :在指定的截止时间之前获取锁, 超过截止时间后仍旧无法获取则返回

Lock接口基本的方法:

方法名称描述

void lock():获得锁。如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获取锁。

void lockInterruptibly():获取锁,如果可用并立即返回。如果锁不可用,那么当前线程将被禁用以进行线程调度,并且处于休眠状态,和lock()方法不同的是在锁的获取中可以中断当前线程(相应中断)。

Condition newCondition():获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait()方法,而调用后,当前线程将释放锁。

boolean tryLock():只有在调用时才可以获得锁。如果可用,则获取锁定,并立即返回值为true;如果锁不可用,则此方法将立即返回值为false 。

boolean tryLock(long time, TimeUnit unit):超时获取锁,当前线程在一下三种情况下会返回: 1. 当前线程在超时时间内获得了锁;2.当前线程在超时时间内被中断;3.超时时间结束,返回false.

void unlock():释放锁。

二 Lock接口的实现类:ReentrantLock

ReentrantLock和synchronized关键字一样可以用来实现线程之间的同步互斥,但是在功能是比synchronized关键字更强大而且更灵活

ReentrantLock类常见方法:

(六)Lock锁的使用_第3张图片

2.1 第一个ReentrantLock程序

ReentrantLockTest.java

(六)Lock锁的使用_第4张图片


(六)Lock锁的使用_第5张图片
(六)Lock锁的使用_第6张图片

运行结果: 

(六)Lock锁的使用_第7张图片

从运行结果可以看出,当一个线程运行完毕后才把锁释放,其他线程才能执行,其他线程的执行顺序是不确定的。

2.2 Condition接口简介

我们通过之前的学习知道了:synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。

在使用notify/notifyAll()方法进行通知时,被通知的线程是有JVM选择的,使用ReentrantLock类结合Condition实例可以实现“选择性通知”,这个功能非常重要,而且是Condition接口默认提供的。

而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程

Condition接口的常见方法:

方法名称描述

void await():相当于Object类的wait方法

boolean await(long time, TimeUnit unit):相当于Object类的wait(long timeout)方法

signal():相当于Object类的notify方法

signalAll():相当于Object类的notifyAll方法

2.3 使用Condition实现等待/通知机制

1. 使用单个Condition实例实现等待/通知机制:

UseSingleConditionWaitNotify.java

注意: 默认情况下ReentranLock类使用的是非公平锁

2.4 公平锁与非公平锁

Lock锁分为:公平锁 和 非公平锁公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序。而非公平锁就是一种获取锁的抢占机制,是随机获取锁的,和公平锁不一样的就是先来的不一定先的到锁,这样可能造成某些线程一直拿不到锁,结果也就是不公平的了。

final Service service = new Service(false);//true为公平锁,false为非公平锁

三 ReadWriteLock接口的实现类:ReentrantReadWriteLock 

3.1简介

我们刚刚接触到的ReentrantLock(排他锁)具有完全互斥排他的效果,即同一时刻只允许一个线程访问,这样做虽然虽然保证了实例变量的线程安全性,但效率非常低下。ReadWriteLock接口的实现类-ReentrantReadWriteLock读写锁就是为了解决这个问题。

读写锁维护了两个锁,一个是读操作相关的锁也成为共享锁,一个是写操作相关的锁 也称为排他锁。通过分离读锁和写锁,其并发性比一般排他锁有了很大提升

多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥(只要出现写操作的过程就是互斥的。)在没有线程Thread进行写入操作时,进行读取操作的多个Thread都可以获取读锁,而进行写入操作的Thread只有在获取写锁后才能进行写入操作。即多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。

3.2 ReentrantReadWriteLock的特性与常见方法 

ReentrantReadWriteLock的特性:

(六)Lock锁的使用_第8张图片

ReentrantReadWriteLock常见方法: 

常见方法: 

和ReentrantLock类 类似这里就不列举了。

3.3 ReentrantReadWriteLock的使用

1. 读读共享

两个线程同时运行read方法,你会发现两个线程可以同时或者说是几乎同时运行lock()方法后面的代码,输出的两句话显示的时间一样。这样提高了程序的运行效率。

2. 写写互斥

lock.readLock().lock();

改为:

lock.writeLock().lock();

两个线程同时运行read方法,你会发现同一时间只允许一个线程执行lock()方法后面的代码

3. 读写互斥

4. 写读互斥

和读写互斥类似,这里不用代码演示了。记住:只要出现写操作的过程就是互斥的。

原文:https://blog.csdn.net/qq_34337272/article/details/79714196

你可能感兴趣的:((六)Lock锁的使用)