ReentrantLock是一种可重入的独占锁,它允许同一个线程多次获取同一个锁而不会被阻塞。它的功能类似于synchronized是一种互斥锁,可以保证线程安全。相对于 synchronized,ReentrantLock具备如下特点:
它的主要应用场景是在多线程环境下对共享资源进行独占式访问,以保证数据的一致性和安全性。
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
ReentrantLock实现了Lock接口规范,常见API如下:
接口 | 作用 |
---|---|
void lock() | 获取锁,调用该方法当前线程会获取锁,当锁获得后,该方法返回 |
void lockInterruptibly() throws InterruptedException | 可中断的获取锁,和lock()方法不同之处在于该方法会响应中断,即在锁的获取中可以中断当前线程 |
boolean tryLock() | 尝试非阻塞的获取锁,调用该方法后立即返回。如果能够获取到返回true,否则返回false |
boolean tryLock(long time, TimeUnit unit) throws InterruptedException | 超时获取锁,当前线程在以下三种情况下会被返回: 当前线程在超时时间内获取了锁 当前线程在超时时间内被中断 超时时间结束,返回false |
Condition newCondition() | 获取等待通知组件,该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的await()方法,而调用后,当前线程将释放锁 |
//加锁 阻塞
lock.lock();
try {
...
} finally {
// 解锁
lock.unlock();
}
//尝试加锁 非阻塞
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
...
} finally {
lock.unlock();
}
}
在使用时要注意 4 个问题:
public class ReentrantLockDemo {
private final ReentrantLock lock = new ReentrantLock();//默认非公平
private static int tickets = 8;
public void buyTicket(){
lock.lock();
try{
if(tickets>0){
try {
Thread.sleep(10);//休眠10ms,模拟出并发效果
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"购买了第"+tickets--+"张票");
}else {
System.out.println("票已经卖完了,"+Thread.currentThread().getName()+"抢票失败");
}
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockDemo ticketSystem = new ReentrantLockDemo();
for(int i = 1;i<=10;i++){
new Thread(()->{
ticketSystem.buyTicket();
},"线程"+i).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("剩余票数:"+tickets);
}
}
/**
*
* 不加锁效果:出现超卖问题
* 线程4购买了第8张票
* 线程6购买了第8张票
* 线程10购买了第5张票
* 线程5购买了第8张票
* 线程9购买了第6张票
* 线程7购买了第7张票
* 线程8购买了第4张票
* 线程3购买了第8张票
* 线程1购买了第3张票
* 线程2购买了第8张票
* 剩余票数:2
*
*
* 加锁效果:正常,两个人抢票失败
* 线程1购买了第8张票
* 线程5购买了第7张票
* 线程6购买了第6张票
* 线程4购买了第5张票
* 线程8购买了第4张票
* 线程3购买了第3张票
* 线程7购买了第2张票
* 线程9购买了第1张票
* 票已经卖完了,线程2抢票失败
* 票已经卖完了,线程10抢票失败
* 剩余票数:0
*/
ReentrantLock支持公平锁和非公平锁两种模式:
ReentrantLock lock = new ReentrantLock();//参数默认false,不公平锁
ReentrantLock lock = new ReentrantLock(true);//公平锁
比如买票的时候就有可能出现插队的场景,允许插队就是非公平锁,如下图:
public class ReentrantLockDemo {
private final UseLock lock = new UseLock();//默认非公平
private static int tickets = 999;
private static Thread nextThread = null;
private class UseLock extends ReentrantLock{
public UseLock(){
super();
}
public UseLock(boolean fair){
super(fair);
}
@Override
public Collection<Thread> getQueuedThreads(){
return super.getQueuedThreads();
}
@Override
public Thread getOwner(){
return super.getOwner();
}
}
public void buyTicket(){
lock.lock();
try {
if(tickets>0){
try {
Thread.sleep(10);//休眠10ms,模拟出并发效果
}catch (InterruptedException e){
e.printStackTrace();
}
Thread tempThread = lock.getQueuedThreads().stream().sorted((pre,next)->-1).collect(Collectors.toList()).get(0);
System.out.println((nextThread==Thread.currentThread()||nextThread==null? "":Thread.currentThread().getName()
+"插队成功")+Thread.currentThread().getName()+"购买了第"+tickets--+"张票"+"-----下一个线程应为:"+tempThread.getName());
//System.out.println(lock.getQueuedThreads().stream().map(e->e.getName()).sorted((pre,next)->-1).collect(Collectors.toList()));
nextThread = tempThread;
}else {
System.out.println("票已经卖完了,"+Thread.currentThread().getName()+"抢票失败");
}
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockDemo ticketSystem = new ReentrantLockDemo();
for(int i = 1;i<=1000;i++){
new Thread(()->{
ticketSystem.buyTicket();
},"线程"+i).start();
Thread.sleep(2);//让线程一个个启动来插队
}
// try {
// Thread.sleep(3000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// System.out.println("剩余票数:"+tickets);
}
}
/**
* 线程1购买了第999张票-----下一个线程应为:线程2
* 线程2购买了第998张票-----下一个线程应为:线程3
* 线程3购买了第997张票-----下一个线程应为:线程4
* 线程4购买了第996张票-----下一个线程应为:线程5
* 线程5购买了第995张票-----下一个线程应为:线程6
* 线程6购买了第994张票-----下一个线程应为:线程7
* 线程7购买了第993张票-----下一个线程应为:线程8
* 线程8购买了第992张票-----下一个线程应为:线程9
* 线程9购买了第991张票-----下一个线程应为:线程10
* 线程10购买了第990张票-----下一个线程应为:线程11
* 线程11购买了第989张票-----下一个线程应为:线程12
* 线程12购买了第988张票-----下一个线程应为:线程13
* 线程13购买了第987张票-----下一个线程应为:线程14
* 线程14购买了第986张票-----下一个线程应为:线程15
* 线程15购买了第985张票-----下一个线程应为:线程16
* 线程16购买了第984张票-----下一个线程应为:线程17
* 线程62插队成功线程62购买了第983张票-----下一个线程应为:线程17
* 线程17购买了第982张票-----下一个线程应为:线程18
* 线程18购买了第981张票-----下一个线程应为:线程19
* 线程19购买了第980张票-----下一个线程应为:线程20
* 线程20购买了第979张票-----下一个线程应为:线程21
* 线程21购买了第978张票-----下一个线程应为:线程22
* 线程22购买了第977张票-----下一个线程应为:线程23
* 线程23购买了第976张票-----下一个线程应为:线程24
* 线程24购买了第975张票-----下一个线程应为:线程25
* 线程25购买了第974张票-----下一个线程应为:线程26
* 线程26购买了第973张票-----下一个线程应为:线程27
* 线程27购买了第972张票-----下一个线程应为:线程28
* 线程28购买了第971张票-----下一个线程应为:线程29
* 线程29购买了第970张票-----下一个线程应为:线程30
* 线程30购买了第969张票-----下一个线程应为:线程31
* 线程31购买了第968张票-----下一个线程应为:线程32
* 线程32购买了第967张票-----下一个线程应为:线程33
* 线程33购买了第966张票-----下一个线程应为:线程34
* 线程34购买了第965张票-----下一个线程应为:线程35
* 线程35购买了第964张票-----下一个线程应为:线程36
* 线程36购买了第963张票-----下一个线程应为:线程37
* 线程37购买了第962张票-----下一个线程应为:线程38
* 线程38购买了第961张票-----下一个线程应为:线程39
* 线程39购买了第960张票-----下一个线程应为:线程40
* 线程40购买了第959张票-----下一个线程应为:线程41
* 线程41购买了第958张票-----下一个线程应为:线程42
* 线程42购买了第957张票-----下一个线程应为:线程43
* 线程43购买了第956张票-----下一个线程应为:线程44
* 线程44购买了第955张票-----下一个线程应为:线程45
* 线程45购买了第954张票-----下一个线程应为:线程46
* 线程46购买了第953张票-----下一个线程应为:线程47
* 线程47购买了第952张票-----下一个线程应为:线程48
* 线程48购买了第951张票-----下一个线程应为:线程49
* 线程49购买了第950张票-----下一个线程应为:线程50
* 线程50购买了第949张票-----下一个线程应为:线程51
* 线程51购买了第948张票-----下一个线程应为:线程52
* 线程52购买了第947张票-----下一个线程应为:线程53
* 线程53购买了第946张票-----下一个线程应为:线程54
* 线程206插队成功线程206购买了第945张票-----下一个线程应为:线程54
* 线程54购买了第944张票-----下一个线程应为:线程55
* 线程55购买了第943张票-----下一个线程应为:线程56
* 线程217插队成功线程217购买了第942张票-----下一个线程应为:线程56
* 线程56购买了第941张票-----下一个线程应为:线程57
* 线程57购买了第940张票-----下一个线程应为:线程58
* 线程228插队成功线程228购买了第939张票-----下一个线程应为:线程58
* 线程58购买了第938张票-----下一个线程应为:线程59
* 线程59购买了第937张票-----下一个线程应为:线程60
* 线程60购买了第936张票-----下一个线程应为:线程61
* 线程61购买了第935张票-----下一个线程应为:线程63
* 线程63购买了第934张票-----下一个线程应为:线程64
* ......
*/
将ReentrantLock改为公平锁以后将没有插队现象
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。在实际开发中,可重入锁常常应用于递归操作、调用同一个类中的其他方法、锁嵌套等场景中。
public class Counter {
private final ReentrantLock lock = new ReentrantLock();//默认非公平
public void recursiveCall(int num){
lock.lock();
try {
if(num==0) {
return;
}
System.out.println("递归执行,num = "+num);
recursiveCall(num-1);
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
Counter counter = new Counter();
counter.recursiveCall(10);
}
}
/**
* 递归执行,num = 10
* 递归执行,num = 9
* 递归执行,num = 8
* 递归执行,num = 7
* 递归执行,num = 6
* 递归执行,num = 5
* 递归执行,num = 4
* 递归执行,num = 3
* 递归执行,num = 2
* 递归执行,num = 1
*/
java.util.concurrent类库中提供Condition类来实现线程之间的协调。调用Condition.await() 方法使线程等待,其他线程调用Condition.signal() 或 Condition.signalAll() 方法唤醒等待的线程。
注意:调用Condition的await()和signal()方法,都必须在lock保护之内。
案例:基于ReentrantLock和Condition实现一个简单队列
public class ConditionDemo {
public static void main(String[] args) {
Queue queue = new Queue(5);
new Thread(new Producer(queue)).start();
new Thread(new Customer(queue)).start();
}
static class Producer implements Runnable{
private Queue queue;
public Producer(Queue queue){this.queue = queue;}
@Override
public void run() {
try {
while(true){
Thread.sleep(new Random().nextInt(500,1000));
queue.put(new Random().nextInt(1000));
}
}catch (Exception e){
e.printStackTrace();
}
}
}
static class Customer implements Runnable{
private Queue queue;
public Customer(Queue queue){this.queue = queue;}
@Override
public void run() {
try {
while(true){
Thread.sleep(new Random().nextInt(500,1000));
System.out.println("customer消费:"+queue.take());
}
}catch (Exception e){
e.printStackTrace();
}
}
}
static class Queue{
private Object[] items;
int size = 0;
int putIndex;
int takeIndex;
private final ReentrantLock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public Queue(int capacity){
this.items = new Object[capacity];
}
public void put(Object value) throws InterruptedException {
lock.lock();
try{
while(size==items.length){
System.out.println("queue队列满了:"+ Arrays.asList(items));
notFull.await();
}
items[putIndex] = value;
if(++putIndex == items.length){
putIndex = 0;
}
size++;
notEmpty.signal();
}finally {
System.out.println("producer生产:" + value);
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while(size==0){
System.out.println("queue队列为null:"+ Arrays.asList(items));
notEmpty.await();
}
Object value = items[takeIndex];
items[takeIndex] = null;
if(++takeIndex == items.length){
takeIndex = 0;
}
size--;
notFull.signal();
return value;
}finally {
lock.unlock();
}
}
}
}
ReentrantLock具体应用场景如下: