1.简述
ReentrantLock 是一个可重入的互斥(/独占)锁,又称为“独占锁”。
其可以完全替代 synchronized 关键字。JDK 1.5.0 引入的,其性能远好于 synchronized,但 JDK 1.6.0 开始,JDK 对 synchronized 做了大量的优化,使得两者差距并不大。但其提供了超出synchonized的其他高级功能(例如,中断锁等候、条件变量等),并且使用ReentrantLock比synchronized能获得更好的可伸缩性。也更灵活。
2.特性
- “独占”,指在同一时刻只能有一个线程获取到锁,而其它获取锁的线程只能处于同步队列中等待,只有获取锁的线程释放了锁,后续的线程才能够获取锁。
- “可重入”,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。
- “选择性”,支持获取锁时的公平和非公平性选择。“公平” 指不同的线程获取锁的机制是公平的”,而“不公平” 指不同的线程获取锁的机制是非公平的”。
3.ReentrantLock常用方法
3.1 方法介绍
序号 | 方法名 | 返回值 | 含义 |
---|---|---|---|
1 | lock() | void | 当前线程-加锁 |
2 | unlock() | void | 当前线程-解锁 |
3 | tryLock() | Boolean | 尝试获得锁,仅在调用时锁未被线程占用,获得锁 |
4 | tryLock(long timeout, TimeUnit unit) | Boolean | 限时的锁等待,一定时间内若扔未获得则返回false |
5 | isLocked | Boolean | 是否锁定 |
6 | isFair() | Boolean | 是否公平 |
7 | lockInterruptibly() | Void | 如果当前线程未被中断,获取锁 |
8 | isHeldByCurrentThread() | Boolean | 当前线程是否保持锁锁定,线程的执行lock方法的前后分别是false和true |
9 | hasQueuedThreads() | Boolean | 是否有线程等待此锁 |
10 | getHoldCount() | int | 当前线程保持此锁的次数,即执行此线程执行lock方法的次数 |
11 | getQueueLength() | int | 返回正等待获取此锁的线程估计数,例如:启动10个线程,1个线程获得锁,此时返回的是9 |
12 | getWaitQueueLength(Condition condition) | i | 返回等待与此锁相关的给定条件的线程估计数。比如10个线程,用同一个condition对象,并且此时这10个线程都执行了condition对象的await方法,那么此时执行此方法返回10 |
13 | hasWaiters(Condition condition) | Boolean | 查询是否有线程等待与此锁有关的给定条件(condition),对于指定contidion对象,有多少线程执行了condition.await方法 |
3.2 tryLock和lock和lockInterruptibly的区别
- tryLock 能获得锁就返回true,不能就立即返回false
- tryLock(long timeout,TimeUnit unit),可以增加时间限制,如果超过该时间段还没获得锁,返回false
- lock 能获得锁就返回true,不能的话一直等待获得锁
- lock和lockInterruptibly,如果两个线程分别执行这两个方法,但此时中断这两个线程,前者不会抛出异常,而后者会抛出异常
4.使用实例
注:在实例中使用了int i ++操作作为延时代码,i++是非线程安全的,
详细了解可参考
https://blog.csdn.net/qq_38011415/article/details/88857201
4.1 lock与unlock
标准使用格式示例
//创建锁
private static ReentrantLock syncLock = new ReentrantLock();
public void test() {
try {
syncLock.lock();
// todo
} finally {
//解锁,勿忘,防止死锁
syncLock.unlock();
}
}
测试代码实例
public static void main(String[] args) throws Exception {
//创建线程池
ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(20);
for (int i = 0; i < 10; i++) {
scheduler.execute(() -> {
long start = System.currentTimeMillis();
//尝试加锁
if (syncLock.tryLock()) {
try {
long end = System.currentTimeMillis();
System.out.println("线程:" + Thread.currentThread().getName() + "获取锁成功了!" + "等待毫秒数:" + (end - start));
// 模拟业务代码 耗时
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
syncLock.unlock();
}
} else {
long end = System.currentTimeMillis();
System.out.println("线程:" + Thread.currentThread().getName() + "获取锁失败了!" + "等待毫秒数:" + (end - start));
}
});
}
scheduler.shutdown();
//保证前面的线程都执行完
while (Thread.activeCount() > 2) {
Thread.yield();
}
}
测试结果
4.1 trylock
测试实例1
注:trylock 获取不到锁立马返回
package com.example.reent.lock;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 码农猿
*/
public class ReentrantLockTest2 {
private static ReentrantLock syncLock = new ReentrantLock();
public static void main(String[] args) throws Exception {
//创建线程池
ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(20);
for (int i = 0; i < 10; i++) {
scheduler.execute(() -> {
//加锁
if (syncLock.tryLock()) {
try {
for (int j = 0; j < 10; j++) {
System.out.println("线程:" + Thread.currentThread().getName() + "获取锁成功了!");
}
// 模拟业务代码 耗时
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
syncLock.unlock();
}
} else {
System.out.println("线程:" + Thread.currentThread().getName() + "获取锁失败了!");
}
});
}
scheduler.shutdown();
//保证前面的线程都执行完
while (Thread.activeCount() > 2) {
Thread.yield();
}
}
}
测试结果
测试实例12
注:trylock(long timeout, TimeUnit unit) 设置获取锁超时时间
public static void main(String[] args) throws Exception {
//创建线程池
ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(20);
for (int i = 0; i < 10; i++) {
scheduler.execute(() -> {
long start = System.currentTimeMillis();
try {
//等待 205毫秒尝试加锁
if (syncLock.tryLock(205, TimeUnit.MILLISECONDS)) {
try {
long end = System.currentTimeMillis();
System.out.println("线程:" + Thread.currentThread().getName() + "获取锁成功了!" + "等待毫秒数:" + (end - start));
// 模拟业务代码 耗时
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
syncLock.unlock();
}
} else {
long end = System.currentTimeMillis();
System.out.println("线程:" + Thread.currentThread().getName() + "获取锁失败了!" + "等待毫秒数:" + (end - start));
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
scheduler.shutdown();
//保证前面的线程都执行完
while (Thread.activeCount() > 2) {
Thread.yield();
}
}
结果图示
总结
优点:
1.加锁和释放锁独立,可以分开来控制。
2.tryLock的方法可以尝试加锁,不会像sychnorized一致阻塞。
3.实现了公平锁和非公平锁。sychnorized只能是非公平锁。
缺点:
1.增加了代码的复杂度。
2.相比sychnorized的自动加锁和释放锁,Lock需要手动,容易忘记,从而出现重大的隐患。