ReentrantLock 的设计初衷是为了提供一种比 synchronized 更加灵活和可控的锁机制。与 synchronized 相比,ReentrantLock 提供了更多的功能,如可重入性、公平锁和中断锁等,使得它在某些场景下更适用。
具体来说,ReentrantLock 可以通过以下方式提供更好的控制和灵活性:
1. 可重入性:允许线程多次获得同一个锁,避免死锁情况的发生。
2. 公平锁:可以实现公平的锁分配机制,避免某些线程长期无法获取到锁而产生的饥饿问题。
3. 可打断:允许使用 interrupt() 方法来中断正在等待获取锁的线程,提高了程序的响应性能力。
4. 条件变量:提供了 Condition 类来支持对线程等待/唤醒操作的高级控制。
由于 ReentrantLock 提供了这些功能,它在某些高并发场景下比 synchronized 更加有优势。
🔓1.可重入性
在下面的示例中,outer()
方法获取锁后调用了inner()
方法,而inner()
方法也会获取同一个锁。由于ReentrantLock是可重入锁,这意味着同一线程可以多次获取锁而不会发生死锁。因此,当inner()
方法获取锁时,它并不会被阻塞,而是继续执行并输出"执行内部方法"。当inner()
方法执行完毕后,它会释放锁,然后outer()
方法也会释放锁
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private final ReentrantLock lock = new ReentrantLock();
public void outer() throws InterruptedException {
lock.lock(); // 获取锁
try {
inner(); // 调用内部方法
} finally {
lock.unlock(); // 释放锁
}
}
public void inner() throws InterruptedException {
lock.lock(); // 获取锁
try {
System.out.println("执行内部方法");
} finally {
lock.unlock(); // 释放锁
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockDemo demo = new ReentrantLockDemo();
demo.outer(); // 调用外部方法
}
}
🔓可打断
ReentrantLock 的 lockInterruptibly() 方法提供了可中断的获取锁操作,如果当前线程正在等待获取锁时,可以使用 interrupt() 方法中断该线程等待并唤醒它。这样,当其他线程需要获取同一个锁时,就可以通过 interrupt() 方法打断某些线程的等待,从而避免死锁问题。
需要注意的是,在使用可中断的获取锁操作时,需要对 InterruptedException 异常进行处理,以便及时释放锁和退出线程等待状态。
@Slf4j
public class ReentrantLockDemo {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
log.info("2.尝试获取锁...");
//如果没有竞争此方法就会获取lock锁对象
//如果有竞争就会进入阻塞队列, 可以使用其他线程用interrupt唤醒
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.info("4.没有获取到锁, 返回");
return;
}
try {
log.info("尝试获取锁");
} finally {
log.info("释放锁~");
lock.unlock();
}
}, "t1");
log.info("1.率先获取锁");
t1.start();
lock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("3.唤醒被阻塞的线程");
t1.interrupt();
}
}
🔓 锁超时
ReentrantLock 的 tryLock() 方法实现锁的超时控制,以及在多线程场景下如何避免死锁问题,提高程序的健壮性和可靠性。
@Slf4j(topic = "ReentrantLockDemo")
public class ReentrantLockDemo {
private static ReentrantLock lock = new ReentrantLock();
//可超时
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.info("尝试获取锁..");
try {
// 尝试获取锁并设定等待时间为1秒
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
log.info("获取不到锁了, 结束..");
return;
}
} catch (Exception e) {
e.printStackTrace();
log.error("获取不到锁: {}", e);
return;
}
try {
log.info("获取到锁了");
} finally {
log.info("开始释放锁..");
lock.unlock();
}
}, "t1");
lock.lock();
log.info("获得到锁了~~~");
t1.start();
}
}
代码中创建了一个静态的 ReentrantLock 对象,然后在主线程中获取该锁。创建了一个线程 t1,在该线程中尝试获取这个锁,并设定等待时间为 1 秒。如果在 1 秒内获取到了锁,则打印获取到锁的信息;否则打印获取不到锁的信息并结束线程。
因此,你的代码演示了如何使用 ReentrantLock 的 tryLock(long timeout, TimeUnit unit) 方法设置锁超时时间,避免线程长时间等待锁而引起死锁问题。
🔓 公平锁与非公平锁
Java中的公平锁和非公平锁的主要区别在于对于线程获取锁的顺序的处理方式。
公平锁保证等待时间最久的线程最先获取锁,即按照请求锁的时间顺序来分配锁。而非公平锁则不考虑等待时间的长短,有可能新请求锁的线程会插队抢占已经持有锁的线程的锁。
使用公平锁会使得所有线程都有平等的机会获取锁,但是会增加系统开销和降低吞吐量。而非公平锁则可以提高系统吞吐量,但是可能会导致某些线程长时间无法获取到锁。
Java中可重入锁(ReentrantLock)默认为非公平锁,但可以通过构造函数参数来指定为公平锁。
这里我们看一下源码就一目了然 :
在这个构造函数中,如果传入的参数fair
为true
,则会创建一个FairSync
对象作为锁的同步器;否则,会创建一个NonfairSync
对象作为锁的同步器。
其中,FairSync
实现了公平锁的逻辑,按照等待时间的顺序授予锁的访问权;而NonfairSync
实现了非公平锁的逻辑,允许新的请求插队抢占已经持有锁的线程的锁。
这里我们举一个业务的例子 :
我们以电商网站的库存为例, 在并发场景下大量用户正在进行抢单操作, 这时候需要对 (库存数量) 进行保护, 避免多个用户同时修改库存数据而导致不一致的问题。 就需要用到公平锁 可以保证每个用户获取锁的机会都是一样的。当多个用户同时请求锁时,锁会按照请求锁的时间顺序进行分配,从而保证了访问资源的顺序是公平的。这种方式适用于业务中对资源访问顺序有明确要求的情况。
另一方面,如果使用非公平锁来保护这些关键资源,可以减少线程阻塞的时间,提高系统的吞吐量。例如,当某个用户请求锁时,如果该锁已经被其他用户持有,那么该用户可以通过插队抢占锁的方式来减少等待时间,从而更快地完成访问资源的操作。这种方式适用于业务中资源的访问顺序没有明确要求,并且希望尽可能提高系统的性能的情况。
扫描下方公众号二维码 回复: 多线程 领取多线程面试题 👇 👇 👇
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)