likes
comments
collection
share

【一文通关】Java多线程基础(8)- ReentrantLock的使用

作者站长头像
站长
· 阅读数 47

ReentrantLock

ReentrantLock的定义

ReentrantLock 是 Java 中实现锁的一种方式,它是一种可重入锁,提供了比 synchronized 更多的灵活性和控制。

ReentrantLock 实现了 java.util.concurrent.locks.Lock 接口,提供了 lock()tryLock() 和 unlock() 等方法来进行加锁和解锁操作。

ReentrantLock 提供了两种加锁方式:公平锁和非公平锁。在构造 ReentrantLock 对象时,可以根据需要选择使用哪种锁,默认是非公平锁。公平锁会按照线程请求锁的顺序进行处理,而非公平锁则允许插队。

ReentrantLock 还提供了一些其他的方法,如 lockInterruptibly()newCondition() 等,它们可以分别实现可中断的加锁和条件变量等功能。

需要注意的是,在使用 ReentrantLock 时需要显式地进行加锁和解锁操作,否则可能会导致死锁等问题。同时,由于 ReentrantLock 的使用相对复杂,需要谨慎使用,避免出现不必要的复杂性。

ReentrantLock的使用

  1. 可以在类中创建锁对象, 如果是希望在多个线程之间共享同一个锁对象,那么需要使用 static 关键字将锁对象声明为静态成员变量,否则每个对象会持有自己独立的锁对象,无法达到同步的效果。因此,在多线程环境中,通常会将锁对象声明为静态的。

  2. 使用时必须显示的进行上锁和释放锁

    上锁: 锁对象.lock();

    解锁: 锁对象.unlock();

    在获取锁之后,必须在 finally 块中使用 unlock() 方法释放锁,以确保在任何情况下都能正确释放锁。

    public void add() {
        // 加锁
        lock.lock();
        try {
            count += 1;
            System.out.println(Thread.currentThread().getName() + ":count加一变为:" + count);
        } finally {
        //解锁
            lock.unlock();
        }
    }
    
  3. 条件变量的使用(condition)

ReentrantLock 还提供了 Condition 接口,可以用于实现等待/通知机制。可以通过 ReentrantLock 的 newCondition() 方法创建一个新的条件变量对象,例如:

Condition condition = lock.newCondition();

然后可以使用 await() 方法使线程等待条件满足,使用 signal() 或 signalAll() 方法来通知等待线程条件已经满足。例如:

class TicketHouse implements Runnable {

    int fiveMount = 2;
    int twentyMount = 0;

    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        if (name.equals("ming")) {
            saleTicket(20);
        } else if (name.equals("hong")) {
            saleTicket(5);
        }
    }

    public void saleTicket(int money) {
        lock.lock();
        try {
            // money为5, 直接购票成功
            if (money == 5) {
                fiveMount += 1;
                System.out.println(Thread.currentThread().getName() + "购票成功");
            } else if (money == 20) {
                // money为20, 需要判断零钱是否足够
                twentyMount += 1;
                while (fiveMount < 3) {
                    System.out.println("零钱不够," + Thread.currentThread().getName() + "在旁边等");
                    condition.await(); // 线程被卡在这个位置
                    System.out.println(Thread.currentThread().getName() + "被激活了");
                }
                fiveMount -= 3;
                System.out.println(Thread.currentThread().getName() + "买票成功,付钱20,找零3张5元");
            }
            // 这一步每次有人来买票都会运行
            // 激活所有等待该方法的线程
            condition.signalAll();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
}
  1. 公平锁和非公平锁

ReentrantLock 支持公平锁和非公平锁两种机制。公平锁会按照线程等待锁的时间来获取锁,保证等待时间最长的线程最先获取锁。而非公平锁则允许插队,当锁释放时,任何线程都有机会获取锁。默认情况下,ReentrantLock 创建的是非公平锁,可以通过构造方法来创建公平锁,例如:

Lock fairLock = new ReentrantLock(true);  // 创建公平锁
Lock unfairLock = new ReentrantLock();  // 创建非公平锁

需要注意的是,公平锁的性能通常比非公平锁差,因为需要维护等待队列,因此在实际使用时,应该根据实际情况选择使用公平锁还是非公平锁。

下面是一个使用公平锁的例子:

public class Main {
    private static ReentrantLock lock = new ReentrantLock(true);

    public static void main(String[] args) {
        Runnable task = () -> {
            System.out.printf("%s is trying to acquire the lock.%n", Thread.currentThread().getName());
            // 锁住main线程
            lock.lock();
            try {
                System.out.printf("%s has acquired the lock.%n", Thread.currentThread().getName());
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.printf("%s has released the lock.%n", Thread.currentThread().getName());
            }
        };

        Thread thread1 = new Thread(task, "Thread 1");
        Thread thread2 = new Thread(task, "Thread 2");
        Thread thread3 = new Thread(task, "Thread 3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}
  1. 尝试获取锁

ReentrantLock可以尝试获取锁,有两种方法可以实现:

  1. tryLock()方法:这个方法会尝试获取锁,如果锁当前没有被其他线程持有,则获取锁成功,返回true;否则获取锁失败,返回false。
  2. tryLock(long timeout, TimeUnit unit)方法:个方法会在指定时间内尝试获取锁,如果在指定时间内锁当前没有被其他线程持有,则获取锁成功返回true;否则获取锁失败,返回false。
// 尝试获取锁,一秒以后没获取到就返回false
if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        ...
    } finally {
        lock.unlock();
    }
}

这些方法的使用可以使程序更加灵活,可以根据实际需求来选择获取锁的方式。需要注意的是,在使用这些方法时,需要据返回值来判断是否成功获取锁并在获取锁成功后,finally块中释放锁以防止出现死锁等问题。

转载自:https://juejin.cn/post/7229808533419393080
评论
请登录