Java并发编程之ReentrantLock可重入锁的实例代码

网友投稿 313 2022-11-04


Java并发编程之ReentrantLock可重入锁的实例代码

目录 1.ReentrantLock可重入锁概述2.可重入3.可打断4.锁超时5.公平锁6.条件变量 Condition

1.ReentrantLock可重入锁概述

相对于 synchronized 它具备如下特点

可中断

synchronized锁加上去不能中断,a线程应用锁,b线程不能取消掉它

可以设置超时时间

synchronized它去获取锁时,如果对方持有锁,那么它就会进入entryList一直等待下去。而可重入锁可以设置超时时间,规定时间内如果获取不到锁,就放弃锁

可以设置为公平锁

防止线程饥饿的情况,即先到先得。如果争抢的人比较多,则可能会发生永远都得不到锁

支持多个条件变量多个waitset(不支持条件一的去a不支持条件二的去b)

synchronized只支持同一个waitset.

与 synchronized 一样,都支持可重入

基本语法

// 获取锁

reentrantLock.lock();

try {

// 临界区

} finally {

// 释放锁

reentrantLock.unlock();

}

synchronized是在关键字的级别来保护临界区,而reentrantLock是在对象的级别保护临界区。临界区即访问共享资源的那段代码。finally中表明不管将来是否出现异常,都会释放锁,释放锁即调用unlock方法。否则无法释放锁,其它线程就永远也获取不了锁。

2.可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁

如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

ReentrantLock和synchronized都是可重入锁。

public class TestReentranLock1 {

static ReentrantLock lock = new ReentrantLock();

public static void main(String[] args) {

method1();

}

public static void method1() {

lock.lock();

try {

System.out.println("execute method1");

method2();

} finally {

lock.unlock();

}

}

public static void method2() {

lock.lock();

try {

System.out.println("execute method2");

method3();

} finally {

lock.unlock();

}

}

public static void method3() {

lock.lock();

try {

System.out.println("execute method3");

} finahttp://lly {

lock.unlock();

}

}

}

execute method1

execute method2

execute method3

3.可打断

可打断是指在等待锁的过程中,其它线程可以用interrupt方法终止我的等待。synchronized锁是不可打断的。

我们要想在等锁的过程中被打断,就要使用lockInterruptibly()方法对lock对象加锁,而不是lock()方法

public class TestReentranLock2 {

public static void main(String[] args) {

ReentrantLock lock = new ReentrantLock();

Thread t1 = new Thread(() -> {

try {

//如果没有竞争,此方法就会获取lock对象的锁

//如果有竞争,就进入阻塞队列等待,可以被其它线程用interrupt打断

System.out.println("尝试获得锁");

lock.lockInterruptibly();

} catch (InterruptedException e) {

e.printStackTrace();

System.out.println("等锁的过程中被打断");

return;

}

try {

System.out.println("t1获得了锁");

} finally {

lock.unlock();

}

}, "t1");

lock.lock();

System.out.println("主线程获得了锁");

t1.start();

try {

try {

sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

t1.interrupt();

System.out.println("执行打断t1");

} finally {

lock.unlock();

}

}

}

主线程获得了锁

尝试获得锁

执行打断t1

等锁的过程中被打断

java.lang.InterruptedException

at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)

at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)

at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)

at cn.yj.jvm.TestReentranLock2.lambda$main$0(TestReentranLock2.java:15)

at java.lang.Thread.run(Thread.java:748)

注意如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断,即不是。即使用lock()方法。

这种方式可以避免死锁情况的发生,避免无休止的等待。

ReentrantLock lock = new ReentrantLock();

Thread t1 = new Thread(() -> {

System.out.println("启动...");

lock.lock();

try {

System.out.println("获得了锁");

} finally {

lock.unlock();

}

}, "t1");

lock.lock();

System.out.println("获得了锁");

t1.start();

try {

sleep(1);

t1.interrupt();

System.out.println("执行打断");

sleep(1);

} finally {

System.out.println("释放了锁");

lock.unlock();

}

4.锁超时

ReentranLock支持可打断,其实就是为了避免死等,这样就可以减少死锁的发生。实际上可打断这种方式属于一种被动的避免死等,是由其它线程interrupt来打断。

而锁超时是主动的方式避免死等的手段。

获取锁用tryLock()方法,即尝试获得锁,如果成功了,它就获得锁,如果失败了,它就可以不去进入阻塞队列等待,它就会返回false,表示没有获得锁。

立刻失败

public static void main(String[] args) {

ReentrantLock lock = new ReentrantLock();

Thread t1 = new Thread(() -> {

System.out.println("启动...");

if (!lock.tryLock()) {

System.out.println("获取不到锁,立刻失败,返回");

return;

}

try {

System.out.println("获得了锁");

} finally {

lock.unlock();

}

}, "t1");

lock.lock();

System.out.println("获得了锁");

t1.start();

try {

try {

sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

} finally {

lock.unlock();

}

}

获得了锁

启动...

获取不到锁,立刻失败,返回

超时失败

lock.tryLock(1,TimeUnit.SECONDS)表示尝试等待1s,如果主线程不释放锁,那么它就会返回false,如果释放了锁,那么它就会返回true.tryLock也支持被打断,被打断时报异常。

ReentrantLock lock = new ReentrantLock();

Thread t1 = new IWIEjWObDThread(() -> {

log.debug("启动...");

try {

if (!lock.tryLock(1, TimeUnit.SECONDS)) {

log.debug("获取等待 1s 后失败,返回");

return;

}

} catch (InterruptedException e) {

e.printStackTrace();

}

try {

log.debug("获得了锁");

} finally {

lock.unlock();

}

}, "t1");

lock.lock();

log.debug("获得了锁");

t1.start();

try {

sleep(2);

} finally {

lock.unlock();

}

输出

18:19:40.537 [main] c.TestTimeout - 获得了锁

18:19:40.544 [t1] c.TestTimeout - 启动...

18:19:41.547 [t1] c.TestTimeout - 获取等待 1s 后失败,返回

5.公平锁

对于synchronized来说,它是不公平的锁。当一个线程持有锁,其他线程就会进入阻塞队列等待,当锁的持有者释放锁的时候,这些线程就会一拥而上,谁先抢到,谁就成为monitor的主人,而不会按照先来先得的规则。

ReentrantLock 默认是不公平的

ReentrantLock有一个带参构造方法。默认是非公平的。

public ReentrantLock(boolean fair) {

sync = fair ? new FairSync() : new NonfairSync();

}

我们可以通过布尔值改成真,来保证它的公平性。即将来阻塞队列里的线程,争抢锁的时候会按照进入阻塞队列的顺序执行,先到先得。

6.条件变量 Condition

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待

ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比

synchronized 是那些不满足条件的线程都在一间休息室等消息

而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒

使用要点:

await 前需要获得锁

await 执行后,会释放锁,进入 conditionObject 等待

await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁

竞争 lock 锁成功后,从 await 后继续执行

signal 相当于 notify,signalAll 相当于 notifyAll

static ReentrantLock lock = new ReentrantLock();

static Condition waitCigaretteQueue = lock.newCondition();

static Condition waitbreakfastQueue = lock.newCondition();

static volatile boolean hasCigrette = false;

static volatile boolean hasBreakfast = false;

public static void main(String[] args) {

new Thread(() -> {

try {

lock.lock();

while (!hasCigrette) {

try {

waitCigaretteQueue.await();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

log.debug("等到了它的烟");

} finally {

lock.unlock();

}

}).start();

new Thread(() -> {

try {

lock.lock();

while (!hasBreakfast) {

try {

waitbreakfastQueue.await();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

log.debug("等到了它的早餐");

} finally {

lock.unlock();

}

}).start();

sleep(1);

sendBreakfast();

sleep(1);

sendCigarette();

}

private static void sendCigarette() {

lock.lock();

try {

log.debug("送烟来了");

hasCigrette = true;

waitCigaretteQueue.signal();

} finally {

lock.unlock();

}

}

private static void sendBreakfast() {

lock.lock();

try {

log.debug("送早餐来了");

hasBreakfast = true;

waitbreakfastQueue.signal();

} finally {

lock.unlock();

}

}

输出

18:52:27.680 [main] c.TestCondition - 送早餐来了

18:52:27.682 [Thread-1] c.TestCondition - 等到了它的早餐

18:52:28.683 [main] c.TestCondition - 送烟来了

18:52:28.683 [Thread-0] c.TestCondition - 等到了它的烟


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:港口查询API(港口查询系统)
下一篇:IIS备份
相关文章

 发表评论

暂时没有评论,来抢沙发吧~