突击并发编程JUC系列-ReentrantLock
突击并发编程JUC系列演示代码地址:
https://github.com/mtcarpenter/JavaTutorial
锁是用来控制多个线程访问共享资源的方式,通过锁可以防止多个线程同时访问共享资源。在 Java1.5
之前实现锁只能使用 synchronized
关键字实现,但是synchronized
隐式获取释放锁,在 1.5
之后官方新增了 lock
接口也是用来实现锁的功能,,它具备与synchronized
关键字类似的同步功能,显式的获取和释放锁。lock
拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized
关键字所不具备的同步特性。
LOCK 方法说明
void lock()
:获取锁,调用该方法当前线程将会获取锁,当锁获得后,从该方法返回void lockInterruptibly() throws InterruptedException
:可中断地获取锁,和lock
方法地不同之处在于该方法会响应中断,即在锁的获取中可以中断当前线程boolean tryLock()
: 尝试非阻塞地获取锁,调用该方法后立刻返回,如果能够获取则返回 true 否则 返回falseboolean tryLock(long time, TimeUnit unit)
:超时地获取锁,当前线程在以下 3 种情况下会返回:- 当前线程在超时时间内获得了锁
- 当前线程在超时时间被中断
- 超时时间结束后,返回 false
void unlock()
: 释放锁Condition newCondition()
:获取锁等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait()
方法,而调用后,当前线程将释放锁。
ReentrantLock 简介
Lock
作为接口类为我们提供一组方法,只能通过的实现类进行 Lock
方法,今天我们就讲讲继承Lock
接口一个可重入的独占锁 ReentrantLock
实现类,ReentrantLock
通过自定义队列同步器(Abstract Queued Sychronized,AQS
)来实现锁的获取与释放。它使用了一个 int 成员变量表示同步状态,通过内置的 FIFO
队列来完成资源获取线程的排队工作,并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。
独占锁指该锁在同一时刻只能被一个线程获取,而获取锁的其他线程只能在同步队列中等待;可重入锁指该锁能够支持一个线程对同一个资源执行多次加锁操作。ReentrantLock
支持公平锁和非公平锁的实现。公平指线程竞争锁的机制是公平的,而非公平指不同的线程获取锁的机制是不公平的。ReentrantLock
不但提供了synchronized
对锁的操作功能,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。
ReentrantLock 方法说明
ReentrantLock()
: 无参ReentrantLock
使用的非公平锁。ReentrantLock(boolean fair)
:ReentrantLock
可以初始化设置是公平锁锁,还是非公平锁。getHoldCount()
:查询当前线程在某个Lock
上的数量,如果当前线程成功获取了Lock
,那么该值大于等于 1;如果没有获取到Lock
的线程调用该方法,则返回值为 0 。isHeldByCurrentThread()
:判断当前线程是否持有某个Lock
,由于Lock
的排他性,因此在某个时刻只有一个线程调用该方法返回 true。isLocked()
:判断Lock
是否已经被线程持有。isFair()
:创建的ReentrantLock
是否为公平锁。hasQueuedThreads()
:在多个线程试图获取Lock
的时候,只有一个线程能够正常获得,其他线程可能(如果使用tryLock()
方法失败则不会进入阻塞)会进入阻塞,该方法的作用就是查询是否有线程正在等待获取锁。hasQueuedThread(Thread thread)
:在等待获取锁的线程中是否包含某个指定的线程。getQueueLength()
:返回当前有多少个线程正在等待获取锁。
伪代码回顾
精彩片段 1 :
class X {
private final ReentrantLock lock = new ReentrantLock();
public void m() {
// 加锁
lock.lock();
try {
// 业务执行
} finally {
// 释放锁
lock.unlock()
}
}
}
精彩片段 2 :
class X {
private final ReentrantLock lock = new ReentrantLock();
public void m() {
//尝试获取锁
if (lock.tryLock()) {
try {
//处理任务 .......
} catch (Exception ex) {
} finally {
//释放锁
lock.unlock();
}
} else {
//else 表示没有获取锁 无需关闭
// ..... 根据实际业务处理 (返回、处理其它逻辑)
}
}
}
lock.tryLock()
: 阻塞式获取锁,如果能够获取则返回 true 否则 返回 false。无法获取也可以根据实际业务进行处理。
案例上手
synchronized 案例
public class LockExample1 {
// 请求总数
public static int requestTotal = 10000;
// 并发计数
public static int count = 0;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
for (int i = 0; i < requestTotal; i++) {
executorService.execute(() -> {
try {
add();
} catch (Exception e) {
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("count = " + count);
}
private static synchronized void add() {
count++;
}
}
// 运行结果:count = 10000
给 add()
方法加上了 synchronized
锁,保证了该方法在并发下也是同步的。
lock() 方法的使用
public class LockExample2 {
// 请求总数
public static int requestTotal = 10000;
// 并发计数
public static int count = 0;
private final static Lock lock = new ReentrantLock();
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
for (int i = 0; i < requestTotal; i++) {
executorService.execute(() -> {
try {
add();
} catch (Exception e) {
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("count = " + count);
}
private static void add() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
// 运行结果:count = 10000
将需要同步的代码放在 lock
和 unlock
之间,使用 lock
一定要记得释放锁。
tryLock() 方法
private static void add() {
if (lock.tryLock()) {
try {
count++;
} finally {
//当获取锁成功时最后一定要记住finally去关闭锁
lock.unlock(); //释放锁
}
} else {
//else时为未获取锁,则无需去关闭锁
//如果不能获取锁,则直接做其他事情
System.out.println(Thread.currentThread().getName() + "没有获取锁");
}
}
通过 tryLock()
方法就发现在并发的情况下会有部分线程无法获取到锁。
tryLock(long timeout, TimeUnit unit) 可以设置超时时间
private static void add() throws InterruptedException {
if (lock.tryLock(1000, TimeUnit.MILLISECONDS)) {
try {
count++;
} finally {
//当获取锁成功时最后一定要记住finally去关闭锁
lock.unlock(); //释放锁
}
} else {
//else时为未获取锁,则无需去关闭锁
//如果不能获取锁,则直接做其他事情
System.out.println(Thread.currentThread().getName() + "没有获取锁");
}
}
ReentrantLock 提供了公平和非公平锁的实现
- 公平锁:
ReentrantLock lock = new ReentrantLock(true)
。是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。 - 非公平锁:
ReentrantLock lock = new ReentrantLock(false)
。如果构造函数不传递参数,则默认是非公平锁。
欢迎关注公众号 山间木匠 , 我是小春哥,从事 Java 后端开发,会一点前端、通过持续输出系列技术文章以文会友,如果本文能为您提供帮助,欢迎大家关注、点赞、分享支持,我们下期再见!