(八) 共享模型之管程【ReentrantLock】
创始人
2024-03-15 06:56:52

相对于 synchronized 具备如下特定:

(1)可中断

(2)可以设置超市时间

(3)可以设置为公平锁

(4)支持多个条件变量

与 synchronized 一样,都支持可重入

 基本语法

一、可重入(P121)

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

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

@Slf4j(topic = "c.Test22")
public class Test22 {private static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {lock.lock();try {log.debug("enter main");m1();} finally {lock.unlock();}}public static void m1(){lock.lock();try {log.debug("enter m1");m2();} finally {lock.unlock();}}public static void m2(){lock.lock();try {log.debug("enter m2");} finally {lock.unlock();}}
}

二、可打断

@Slf4j(topic = "c.Test22")
public class Test22 {private static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {//如果没有竞争那么此方法就会获取lock对象锁//如果有竞争就进入阻塞队列,可以被其它线程用interruput方法打断log.debug("尝试获取锁");lock.lockInterruptibly();} catch (InterruptedException e) {e.printStackTrace();log.debug("没有获得锁,返回");return;}try {log.debug("获取到锁");} finally {lock.unlock();}},"t1");lock.lock();t1.start();Thread.sleep(1000);log.debug("打断 t1");t1.interrupt();}
}
注意如果是不可中断(lock()方法)模式,那么即使使用了 interrupt 也不会让等待中断

三、锁超时

@Slf4j(topic = "c.Test22")
public class Test22 {private static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {log.debug("尝试获取锁");// 立刻尝试获取锁boolean tryLock = lock.tryLock();if (!tryLock){log.debug("获取不到锁");return;}try {log.debug("获得到锁");} finally {lock.unlock();}},"t1");log.debug("获取锁");lock.lock();t1.start();}
}

 

@Slf4j(topic = "c.Test22")
public class Test22 {private static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {log.debug("尝试获取锁");try {// 2s后尝试获取锁boolean tryLock = lock.tryLock(2, TimeUnit.SECONDS);if (!tryLock){log.debug("获取不到锁");return;}} catch (InterruptedException e) {e.printStackTrace();log.debug("获取不到锁");return;}try {log.debug("获得到锁");} finally {lock.unlock();}},"t1");log.debug("获取锁");lock.lock();t1.start();}
}

使用 tryLock 解决哲学家就餐问题
@Slf4j(topic = "c.Test23")
public class Test23 {public static void main(String[] args) {Chopstick c1 = new Chopstick("1");Chopstick c2 = new Chopstick("2");Chopstick c3 = new Chopstick("3");Chopstick c4 = new Chopstick("4");Chopstick c5 = new Chopstick("5");new Philosopher("苏格拉底", c1, c2).start();new Philosopher("柏拉图", c2, c3).start();new Philosopher("亚里士多德", c3, c4).start();new Philosopher("赫拉克利特", c4, c5).start();new Philosopher("阿基米德", c5, c1).start();
}
}@Slf4j(topic = "c.Philosopher")
class Philosopher extends Thread {Chopstick left;Chopstick right;public Philosopher(String name, Chopstick left, Chopstick right) {super(name);this.left = left;this.right = right;}@Overridepublic void run() {while (true) {// 尝试获得左手筷子if(left.tryLock()) {try {// 尝试获得右手筷子if(right.tryLock()) {try {eat();} finally {right.unlock();}}} finally {left.unlock(); // 释放自己手里的筷子}}}}Random random = new Random();private void eat() {log.debug("eating...");Sleeper.sleep(0.5);}
}class Chopstick extends ReentrantLock {String name;public Chopstick(String name) {this.name = name;}@Overridepublic String toString() {return "筷子{" + name + '}';}
}

四、公平锁

ReentrantLock 默认是不公平的

// true:公平
// false(默认):不公平
ReentrantLock lock = new ReentrantLock(true);

公平锁一般没有必要,会降低并发度,后面分析原理时会讲解。

五、条件变量

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

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

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

(2)而 ReentrantLock 支持多间休息室,唤醒时也是按休息室来唤醒的

使用要点:

(1)await 前需要获得锁

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

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

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

@Slf4j(topic = "c.Test22")
public class Test22 {private static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {// 创建一个新的条件变量(休息室)Condition condition1 = lock.newCondition();Condition condition2 = lock.newCondition();lock.lock();// 进入休息室等待condition1.await();condition1.signal();condition1.signalAll();}
}
@Slf4j(topic = "c.Test24")
public class Test24 {static final Object room = new Object();static boolean hasCigarette = false;static boolean hasTakeout = false;static ReentrantLock ROOM = new ReentrantLock();// 等待烟的休息室static Condition waitCigaretteSet = ROOM.newCondition();// 等外卖的休息室static Condition waitTakeoutSet = ROOM.newCondition();public static void main(String[] args) {new Thread(() -> {ROOM.lock();try {log.debug("有烟没?[{}]", hasCigarette);while (!hasCigarette) {log.debug("没烟,先歇会!");try {waitCigaretteSet.await();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("可以开始干活了");} finally {ROOM.unlock();}}, "小南").start();new Thread(() -> {ROOM.lock();try {log.debug("外卖送到没?[{}]", hasTakeout);while (!hasTakeout) {log.debug("没外卖,先歇会!");try {waitTakeoutSet.await();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("可以开始干活了");} finally {ROOM.unlock();}}, "小女").start();sleep(1);new Thread(() -> {ROOM.lock();try {hasTakeout = true;waitTakeoutSet.signal();} finally {ROOM.unlock();}}, "送外卖的").start();sleep(1);new Thread(() -> {ROOM.lock();try {hasCigarette = true;waitCigaretteSet.signal();} finally {ROOM.unlock();}}, "送烟的").start();}}

六、同步模式之顺序控制

1. 固定运行顺序

1.1 wait notify

@Slf4j(topic = "c.Test25")
public class Test25 {static final Object lock = new Object();// 表示 t2 是否运行过static boolean t2runned = false;public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (lock) {while (!t2runned) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("1");}}, "t1");Thread t2 = new Thread(() -> {synchronized (lock) {log.debug("2");t2runned = true;lock.notify();}}, "t2");t1.start();t2.start();}
}

1.2 Park Unpark

@Slf4j(topic = "c.Test26")
public class Test26 {public static void main(String[] args) {Thread t1 = new Thread(() -> {LockSupport.park();log.debug("1");}, "t1");t1.start();new Thread(() -> {log.debug("2");LockSupport.unpark(t1);},"t2").start();}
}
park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和『恢复』, 不需要『同步对象』和『运行标记』

2. 交替输出

线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现

2.1 wait notify

@Slf4j(topic = "c.Test27")
public class Test27 {public static void main(String[] args) {WaitNotify wn = new WaitNotify(1, 5);new Thread(() -> {wn.print("a", 1, 2);}).start();new Thread(() -> {wn.print("b", 2, 3);}).start();new Thread(() -> {wn.print("c", 3, 1);}).start();}
}/*
输出内容       等待标记     下一个标记a           1             2b           2             3c           3             1*/
class WaitNotify {// 打印               a           1             2public void print(String str, int waitFlag, int nextFlag) {for (int i = 0; i < loopNumber; i++) {synchronized (this) {while(flag != waitFlag) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print(str);flag = nextFlag;this.notifyAll();}}}// 等待标记private int flag; // 2// 循环次数private int loopNumber;public WaitNotify(int flag, int loopNumber) {this.flag = flag;this.loopNumber = loopNumber;}
}

2.2 Lock 条件变量版

public class Test30 {public static void main(String[] args) throws InterruptedException {AwaitSignal awaitSignal = new AwaitSignal(5);Condition a = awaitSignal.newCondition();Condition b = awaitSignal.newCondition();Condition c = awaitSignal.newCondition();new Thread(() -> {awaitSignal.print("a", a, b);}).start();new Thread(() -> {awaitSignal.print("b", b, c);}).start();new Thread(() -> {awaitSignal.print("c", c, a);}).start();Thread.sleep(1000);awaitSignal.lock();try {System.out.println("开始...");a.signal();} finally {awaitSignal.unlock();}}
}class AwaitSignal extends ReentrantLock{private int loopNumber;public AwaitSignal(int loopNumber) {this.loopNumber = loopNumber;}// 参数1 打印内容, 参数2 进入哪一间休息室, 参数3 下一间休息室public void print(String str, Condition current, Condition next) {for (int i = 0; i < loopNumber; i++) {lock();try {current.await();System.out.print(str);next.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {unlock();}}}
}

2.3 Park Unpark

@Slf4j(topic = "c.Test31")
public class Test31 {static Thread t1;static Thread t2;static Thread t3;public static void main(String[] args) {ParkUnpark pu = new ParkUnpark(5);t1 = new Thread(() -> {pu.print("a", t2);});t2 = new Thread(() -> {pu.print("b", t3);});t3 = new Thread(() -> {pu.print("c", t1);});t1.start();t2.start();t3.start();LockSupport.unpark(t1);}
}class ParkUnpark {public void print(String str, Thread next) {for (int i = 0; i < loopNumber; i++) {LockSupport.park();System.out.print(str);LockSupport.unpark(next);}}private int loopNumber;public ParkUnpark(int loopNumber) {this.loopNumber = loopNumber;}
}

相关内容

热门资讯

应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...