【一文通关】Java多线程基础(8)- ReentrantLock的使用
ReentrantLock
ReentrantLock的定义
ReentrantLock
是 Java 中实现锁的一种方式,它是一种可重入锁,提供了比 synchronized
更多的灵活性和控制。
ReentrantLock
实现了 java.util.concurrent.locks.Lock
接口,提供了 lock()
、tryLock()
和 unlock()
等方法来进行加锁和解锁操作。
ReentrantLock
提供了两种加锁方式:公平锁和非公平锁。在构造 ReentrantLock
对象时,可以根据需要选择使用哪种锁,默认是非公平锁。公平锁会按照线程请求锁的顺序进行处理,而非公平锁则允许插队。
ReentrantLock
还提供了一些其他的方法,如 lockInterruptibly()
、newCondition()
等,它们可以分别实现可中断的加锁和条件变量等功能。
需要注意的是,在使用 ReentrantLock
时需要显式地进行加锁和解锁操作,否则可能会导致死锁等问题。同时,由于 ReentrantLock
的使用相对复杂,需要谨慎使用,避免出现不必要的复杂性。
ReentrantLock的使用
-
可以在类中创建锁对象, 如果是希望在多个线程之间共享同一个锁对象,那么需要使用
static
关键字将锁对象声明为静态成员变量,否则每个对象会持有自己独立的锁对象,无法达到同步的效果。因此,在多线程环境中,通常会将锁对象声明为静态的。 -
使用时必须显示的进行上锁和释放锁
上锁: 锁对象.lock();
解锁: 锁对象.unlock();
在获取锁之后,必须在
finally
块中使用unlock()
方法释放锁,以确保在任何情况下都能正确释放锁。public void add() { // 加锁 lock.lock(); try { count += 1; System.out.println(Thread.currentThread().getName() + ":count加一变为:" + count); } finally { //解锁 lock.unlock(); } }
-
条件变量的使用(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();
}
}
}
- 公平锁和非公平锁
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();
}
}
- 尝试获取锁
ReentrantLock可以尝试获取锁,有两种方法可以实现:
- tryLock()方法:这个方法会尝试获取锁,如果锁当前没有被其他线程持有,则获取锁成功,返回true;否则获取锁失败,返回false。
- tryLock(long timeout, TimeUnit unit)方法:个方法会在指定时间内尝试获取锁,如果在指定时间内锁当前没有被其他线程持有,则获取锁成功返回true;否则获取锁失败,返回false。
// 尝试获取锁,一秒以后没获取到就返回false
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
...
} finally {
lock.unlock();
}
}
这些方法的使用可以使程序更加灵活,可以根据实际需求来选择获取锁的方式。需要注意的是,在使用这些方法时,需要据返回值来判断是否成功获取锁并在获取锁成功后,finally块中释放锁以防止出现死锁等问题。
转载自:https://juejin.cn/post/7229808533419393080