1.基本概念

1.1.什么事可重入锁?

ReentrantLock是Java.util.concurrent.locks包下的一个类,它实现Lock 接口,提供比 synchronized 更灵活、强大的锁机制。"可重入"意味着同一个线程可以多次获取同一把锁,而不会被阻塞。

1.2.为什么需要ReentranLock?

相比于synchronized而言,ReentranLock提供了以下优势:

(1)同时支持公平锁和非公平锁;

(2)获取锁的过程,可以中断;

(3)获取锁的过程中,可以设置超时;

(4)支持多个条件变量(Condition);

(5)支持非阻塞地方尝试获取锁;

2.核心方法

2.1.锁的操作方法

2.1.1构造方法

// 创建一个非公平锁
ReentrantLock rtl= new ReentrantLock() 

// 根据公平策略创建锁
ReentrantLock rtl= new ReentrantLock(boolean fair) 

2.1.2.其他方法

方法 描述 是否可中断 是否阻塞 是否带超时
void lock() 获取锁,若锁被占用则阻塞等待 ❌ 不可中断 ✅ 阻塞 ❌ 无超时
void lockInterruptibly() 获取锁,阻塞时可响应中断 ✅ 可中断 ✅ 阻塞 ❌ 无超时
boolean tryLock() 尝试获取锁,立即返回成功或失败 ❌ 不可中断 ❌ 非阻塞 ❌ 无超时
boolean tryLock(long timeout, TimeUnit unit) 尝试获取锁,带超时等待 ✅ 可中断 ✅ 超时前阻塞 ✅ 可设超时
void unlock() 释放锁 - - -
int getHoldCount() 返回当前线程重入锁的次数 - - -
boolean isHeldByCurrentThread() 检查当前线程是否持有锁 - - -
boolean isLocked() 检查锁是否被任意线程持有 - - -
boolean isFair() 判断是否为公平锁 - - -
getQueuedThreads() 返回等待获取锁的线程集合 - - -
int getQueueLength() 返回等待获取锁的线程数 - - -
boolean hasWaiters(Condition condition) 检查是否有线程在等待指定条件 - - -

2.2条件变量方法

根据创建的ReentrantLock对象,创建条件变量

ReentrantLock lock=new ReentrantLock();
Condition condition = lock.newCondition();

补充:Condition的主要用法

 主要方法

方法 作用 对应Object方法
await() 释放锁并等待 wait()
await(long time, TimeUnit unit) 限时等待 wait(long timeout)
awaitNanos(long nanosTimeout) 纳秒级限时等待 -
awaitUninterruptibly() 不可中断的等待 -
signal() 唤醒一个等待线程 notify()
signalAll() 唤醒所有等待线程 notifyAll()

3.使用案例

3.1锁的基础使用

package Lock;

import java.util.concurrent.locks.ReentrantLock;

//ReentrantLock
public class Demo01 {
    public static void main(String[] args) {
        Counter counter = new Counter();
        Counter counter2 = new Counter();

        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
              counter.add();
            }
        });

        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
              counter2.sub();
            }
        });

        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main=>"+counter.countValue);

    }
}
class Counter {
    public static int countValue = 0;

    //锁对象
    public static final ReentrantLock lock = new ReentrantLock();

    public void add() {
        lock.lock();//加锁
        try {
            for (int i = 0; i < 10000; i++) {
                countValue++;
            }
        } finally {
            lock.unlock();//释放锁
        }
    }

    public void sub() {
        lock.lock();
        try {
            for (int i = 0; i < 10000; i++) {
                countValue--;
            }
        } finally {
            lock.unlock();
        }
    }
}

运行结果:

要使用ReentrantLock就要创建锁对象,此段代码两个线程共用了一个锁对象

package Lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Demo02 {
    public static void main(String[] args) {
        TicketPool ticketPool=new TicketPool(20);

        Thread t1=new Thread(ticketPool,"窗口1");
        Thread t2=new Thread(ticketPool,"窗口2");
        Thread t3=new Thread(ticketPool,"窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class TicketPool implements Runnable{
    //当前剩余门票数量
    private int ticketNumber ;

    //锁对象
    private final ReentrantLock lock = new ReentrantLock();

   // 条件对象
    private final Condition condition = lock.newCondition();

    public TicketPool(int ticketNumber) {
        this.ticketNumber = ticketNumber;
    }

    @Override
    public void run() {
        //售票逻辑
        System.out.println(Thread.currentThread().getName()+"准备开始售票");

        lock.lock();
        try {
            while(true){
                if (ticketNumber <=0){
                    System.out.println(Thread.currentThread().getName()+"票已售完");
                    return;
                }else {
                    System.out.println(Thread.currentThread().getName()+"正在出售第"+ticketNumber+"张票,还剩下"+--ticketNumber+"张票");
                    try {
                        condition.await(1, TimeUnit.SECONDS);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }finally {
            lock.unlock();
        }
    }
}

4.ReentrantLock内部结构

ReentrantLock实现了 Lock接,Lock接中定义了lock()、unlock() ,tryLock()等相关操作。

ReentrantLock总共有三个内部类:Sync,NofairSync,FairSync

Nonfairsync 类继承了 sync类,表示采用非公平策略获取锁:每一次都尝试获取锁,不会按照公平等待的原则进行等待,不会让等待时间最久的线程获得锁。

Fairsync 类也继承了 sync 类,表示采用公平策略获取锁:当资源空闲时,它总是会先判断同步队列中,是否有等待时间更长的线程,如果存在,则将当前线程加入到等待队列的尾部,实现了公平获取原则。

ReentrantLock 构造函数:默认是采用的非公平策略获取锁。

5.Synchronized和ReentrantLock的区别

特性 synchronized (内置锁) ReentrantLock (显式锁)
实现方式 JVM 关键字,自动管理 JDK 类,需要手动编码实现
锁获取 隐式获取和释放 必须显式调用 lock() 和 unlock()
可中断性 不可中断 支持 lockInterruptibly() 可中断等待
公平性 非公平锁 可选择公平/非公平(构造方法参数)
条件队列 单一等待队列 可创建多个 Condition 实现精细控制
性能 JDK6 后优化,性能相当 高竞争时表现更好
锁绑定条件 只能绑定一个监视器 可绑定多个 Condition
锁状态查询 无法查询 提供 tryLock()isLocked() 等方法
超时机制 不支持 支持 tryLock(timeout) 超时获取
代码灵活性 语法简单,但控制粒度粗 控制精细,但需要手动释放
异常处理 自动释放锁 必须在 finally 中释放锁
适用场景 简单同步场景 复杂同步需求(如公平性、可中断、多条件等)
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐