• (八) 共享模型之管程【ReentrantLock】


    相对于 synchronized 具备如下特定:

    (1)可中断

    (2)可以设置超时时间

    (3)可以设置为公平锁

    (4)支持多个条件变量

    与 synchronized 一样,都支持可重入

     基本语法

    一、可重入(P121)

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

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

    1. @Slf4j(topic = "c.Test22")
    2. public class Test22 {
    3. private static ReentrantLock lock = new ReentrantLock();
    4. public static void main(String[] args) {
    5. lock.lock();
    6. try {
    7. log.debug("enter main");
    8. m1();
    9. } finally {
    10. lock.unlock();
    11. }
    12. }
    13. public static void m1(){
    14. lock.lock();
    15. try {
    16. log.debug("enter m1");
    17. m2();
    18. } finally {
    19. lock.unlock();
    20. }
    21. }
    22. public static void m2(){
    23. lock.lock();
    24. try {
    25. log.debug("enter m2");
    26. } finally {
    27. lock.unlock();
    28. }
    29. }
    30. }

    二、可打断

    1. @Slf4j(topic = "c.Test22")
    2. public class Test22 {
    3. private static ReentrantLock lock = new ReentrantLock();
    4. public static void main(String[] args) throws InterruptedException {
    5. Thread t1 = new Thread(() -> {
    6. try {
    7. //如果没有竞争那么此方法就会获取lock对象锁
    8. //如果有竞争就进入阻塞队列,可以被其它线程用interruput方法打断
    9. log.debug("尝试获取锁");
    10. lock.lockInterruptibly();
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. log.debug("没有获得锁,返回");
    14. return;
    15. }
    16. try {
    17. log.debug("获取到锁");
    18. } finally {
    19. lock.unlock();
    20. }
    21. },"t1");
    22. lock.lock();
    23. t1.start();
    24. Thread.sleep(1000);
    25. log.debug("打断 t1");
    26. t1.interrupt();
    27. }
    28. }
    注意如果是 不可中断(lock()方法)模式,那么即使使用了 interrupt 也不会让等待中断

    三、锁超时

    1. @Slf4j(topic = "c.Test22")
    2. public class Test22 {
    3. private static ReentrantLock lock = new ReentrantLock();
    4. public static void main(String[] args) throws InterruptedException {
    5. Thread t1 = new Thread(() -> {
    6. log.debug("尝试获取锁");
    7. // 立刻尝试获取锁
    8. boolean tryLock = lock.tryLock();
    9. if (!tryLock){
    10. log.debug("获取不到锁");
    11. return;
    12. }
    13. try {
    14. log.debug("获得到锁");
    15. } finally {
    16. lock.unlock();
    17. }
    18. },"t1");
    19. log.debug("获取锁");
    20. lock.lock();
    21. t1.start();
    22. }
    23. }

    1. @Slf4j(topic = "c.Test22")
    2. public class Test22 {
    3. private static ReentrantLock lock = new ReentrantLock();
    4. public static void main(String[] args) throws InterruptedException {
    5. Thread t1 = new Thread(() -> {
    6. log.debug("尝试获取锁");
    7. try {
    8. // 2s后尝试获取锁
    9. boolean tryLock = lock.tryLock(2, TimeUnit.SECONDS);
    10. if (!tryLock){
    11. log.debug("获取不到锁");
    12. return;
    13. }
    14. } catch (InterruptedException e) {
    15. e.printStackTrace();
    16. log.debug("获取不到锁");
    17. return;
    18. }
    19. try {
    20. log.debug("获得到锁");
    21. } finally {
    22. lock.unlock();
    23. }
    24. },"t1");
    25. log.debug("获取锁");
    26. lock.lock();
    27. t1.start();
    28. }
    29. }

    使用 tryLock 解决哲学家就餐问题
    1. @Slf4j(topic = "c.Test23")
    2. public class Test23 {public static void main(String[] args) {
    3. Chopstick c1 = new Chopstick("1");
    4. Chopstick c2 = new Chopstick("2");
    5. Chopstick c3 = new Chopstick("3");
    6. Chopstick c4 = new Chopstick("4");
    7. Chopstick c5 = new Chopstick("5");
    8. new Philosopher("苏格拉底", c1, c2).start();
    9. new Philosopher("柏拉图", c2, c3).start();
    10. new Philosopher("亚里士多德", c3, c4).start();
    11. new Philosopher("赫拉克利特", c4, c5).start();
    12. new Philosopher("阿基米德", c5, c1).start();
    13. }
    14. }
    15. @Slf4j(topic = "c.Philosopher")
    16. class Philosopher extends Thread {
    17. Chopstick left;
    18. Chopstick right;
    19. public Philosopher(String name, Chopstick left, Chopstick right) {
    20. super(name);
    21. this.left = left;
    22. this.right = right;
    23. }
    24. @Override
    25. public void run() {
    26. while (true) {
    27. // 尝试获得左手筷子
    28. if(left.tryLock()) {
    29. try {
    30. // 尝试获得右手筷子
    31. if(right.tryLock()) {
    32. try {
    33. eat();
    34. } finally {
    35. right.unlock();
    36. }
    37. }
    38. } finally {
    39. left.unlock(); // 释放自己手里的筷子
    40. }
    41. }
    42. }
    43. }
    44. Random random = new Random();
    45. private void eat() {
    46. log.debug("eating...");
    47. Sleeper.sleep(0.5);
    48. }
    49. }
    50. class Chopstick extends ReentrantLock {
    51. String name;
    52. public Chopstick(String name) {
    53. this.name = name;
    54. }
    55. @Override
    56. public String toString() {
    57. return "筷子{" + name + '}';
    58. }
    59. }

    四、公平锁

    ReentrantLock 默认是不公平的
    1. // true:公平
    2. // false(默认):不公平
    3. ReentrantLock lock = new ReentrantLock(true);

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

    五、条件变量

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

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

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

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

    使用要点:

    (1)await 前需要获得锁

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

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

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

    1. @Slf4j(topic = "c.Test22")
    2. public class Test22 {
    3. private static ReentrantLock lock = new ReentrantLock();
    4. public static void main(String[] args) throws InterruptedException {
    5. // 创建一个新的条件变量(休息室)
    6. Condition condition1 = lock.newCondition();
    7. Condition condition2 = lock.newCondition();
    8. lock.lock();
    9. // 进入休息室等待
    10. condition1.await();
    11. condition1.signal();
    12. condition1.signalAll();
    13. }
    14. }
    1. @Slf4j(topic = "c.Test24")
    2. public class Test24 {
    3. static final Object room = new Object();
    4. static boolean hasCigarette = false;
    5. static boolean hasTakeout = false;
    6. static ReentrantLock ROOM = new ReentrantLock();
    7. // 等待烟的休息室
    8. static Condition waitCigaretteSet = ROOM.newCondition();
    9. // 等外卖的休息室
    10. static Condition waitTakeoutSet = ROOM.newCondition();
    11. public static void main(String[] args) {
    12. new Thread(() -> {
    13. ROOM.lock();
    14. try {
    15. log.debug("有烟没?[{}]", hasCigarette);
    16. while (!hasCigarette) {
    17. log.debug("没烟,先歇会!");
    18. try {
    19. waitCigaretteSet.await();
    20. } catch (InterruptedException e) {
    21. e.printStackTrace();
    22. }
    23. }
    24. log.debug("可以开始干活了");
    25. } finally {
    26. ROOM.unlock();
    27. }
    28. }, "小南").start();
    29. new Thread(() -> {
    30. ROOM.lock();
    31. try {
    32. log.debug("外卖送到没?[{}]", hasTakeout);
    33. while (!hasTakeout) {
    34. log.debug("没外卖,先歇会!");
    35. try {
    36. waitTakeoutSet.await();
    37. } catch (InterruptedException e) {
    38. e.printStackTrace();
    39. }
    40. }
    41. log.debug("可以开始干活了");
    42. } finally {
    43. ROOM.unlock();
    44. }
    45. }, "小女").start();
    46. sleep(1);
    47. new Thread(() -> {
    48. ROOM.lock();
    49. try {
    50. hasTakeout = true;
    51. waitTakeoutSet.signal();
    52. } finally {
    53. ROOM.unlock();
    54. }
    55. }, "送外卖的").start();
    56. sleep(1);
    57. new Thread(() -> {
    58. ROOM.lock();
    59. try {
    60. hasCigarette = true;
    61. waitCigaretteSet.signal();
    62. } finally {
    63. ROOM.unlock();
    64. }
    65. }, "送烟的").start();
    66. }
    67. }

    六、同步模式之顺序控制

    1. 固定运行顺序

    1.1 wait notify

    1. @Slf4j(topic = "c.Test25")
    2. public class Test25 {
    3. static final Object lock = new Object();
    4. // 表示 t2 是否运行过
    5. static boolean t2runned = false;
    6. public static void main(String[] args) {
    7. Thread t1 = new Thread(() -> {
    8. synchronized (lock) {
    9. while (!t2runned) {
    10. try {
    11. lock.wait();
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. }
    15. }
    16. log.debug("1");
    17. }
    18. }, "t1");
    19. Thread t2 = new Thread(() -> {
    20. synchronized (lock) {
    21. log.debug("2");
    22. t2runned = true;
    23. lock.notify();
    24. }
    25. }, "t2");
    26. t1.start();
    27. t2.start();
    28. }
    29. }

    1.2 Park Unpark

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

    2. 交替输出

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

    2.1 wait notify

    1. @Slf4j(topic = "c.Test27")
    2. public class Test27 {
    3. public static void main(String[] args) {
    4. WaitNotify wn = new WaitNotify(1, 5);
    5. new Thread(() -> {
    6. wn.print("a", 1, 2);
    7. }).start();
    8. new Thread(() -> {
    9. wn.print("b", 2, 3);
    10. }).start();
    11. new Thread(() -> {
    12. wn.print("c", 3, 1);
    13. }).start();
    14. }
    15. }
    16. /*
    17. 输出内容 等待标记 下一个标记
    18. a 1 2
    19. b 2 3
    20. c 3 1
    21. */
    22. class WaitNotify {
    23. // 打印 a 1 2
    24. public void print(String str, int waitFlag, int nextFlag) {
    25. for (int i = 0; i < loopNumber; i++) {
    26. synchronized (this) {
    27. while(flag != waitFlag) {
    28. try {
    29. this.wait();
    30. } catch (InterruptedException e) {
    31. e.printStackTrace();
    32. }
    33. }
    34. System.out.print(str);
    35. flag = nextFlag;
    36. this.notifyAll();
    37. }
    38. }
    39. }
    40. // 等待标记
    41. private int flag; // 2
    42. // 循环次数
    43. private int loopNumber;
    44. public WaitNotify(int flag, int loopNumber) {
    45. this.flag = flag;
    46. this.loopNumber = loopNumber;
    47. }
    48. }

    2.2 Lock 条件变量版

    1. public class Test30 {
    2. public static void main(String[] args) throws InterruptedException {
    3. AwaitSignal awaitSignal = new AwaitSignal(5);
    4. Condition a = awaitSignal.newCondition();
    5. Condition b = awaitSignal.newCondition();
    6. Condition c = awaitSignal.newCondition();
    7. new Thread(() -> {
    8. awaitSignal.print("a", a, b);
    9. }).start();
    10. new Thread(() -> {
    11. awaitSignal.print("b", b, c);
    12. }).start();
    13. new Thread(() -> {
    14. awaitSignal.print("c", c, a);
    15. }).start();
    16. Thread.sleep(1000);
    17. awaitSignal.lock();
    18. try {
    19. System.out.println("开始...");
    20. a.signal();
    21. } finally {
    22. awaitSignal.unlock();
    23. }
    24. }
    25. }
    26. class AwaitSignal extends ReentrantLock{
    27. private int loopNumber;
    28. public AwaitSignal(int loopNumber) {
    29. this.loopNumber = loopNumber;
    30. }
    31. // 参数1 打印内容, 参数2 进入哪一间休息室, 参数3 下一间休息室
    32. public void print(String str, Condition current, Condition next) {
    33. for (int i = 0; i < loopNumber; i++) {
    34. lock();
    35. try {
    36. current.await();
    37. System.out.print(str);
    38. next.signal();
    39. } catch (InterruptedException e) {
    40. e.printStackTrace();
    41. } finally {
    42. unlock();
    43. }
    44. }
    45. }
    46. }

    2.3 Park Unpark

    1. @Slf4j(topic = "c.Test31")
    2. public class Test31 {
    3. static Thread t1;
    4. static Thread t2;
    5. static Thread t3;
    6. public static void main(String[] args) {
    7. ParkUnpark pu = new ParkUnpark(5);
    8. t1 = new Thread(() -> {
    9. pu.print("a", t2);
    10. });
    11. t2 = new Thread(() -> {
    12. pu.print("b", t3);
    13. });
    14. t3 = new Thread(() -> {
    15. pu.print("c", t1);
    16. });
    17. t1.start();
    18. t2.start();
    19. t3.start();
    20. LockSupport.unpark(t1);
    21. }
    22. }
    23. class ParkUnpark {
    24. public void print(String str, Thread next) {
    25. for (int i = 0; i < loopNumber; i++) {
    26. LockSupport.park();
    27. System.out.print(str);
    28. LockSupport.unpark(next);
    29. }
    30. }
    31. private int loopNumber;
    32. public ParkUnpark(int loopNumber) {
    33. this.loopNumber = loopNumber;
    34. }
    35. }

  • 相关阅读:
    【python数据分析刷题】-N07.合并
    长沙理工大学开通CnOpenData试用
    python处理excel
    分类分析|KNN分类模型及其Python实现
    牛客前端宝典——刷题 ##Day10
    从新零售到社区团购,这中间发生了多少变化?
    C#与数据库连接
    cookie加密8
    浅谈一级机电管道设计中的压力与介质温度
    【ARXML专题】-26-Bit Rate相关参数:Tq,SJW,Sample Point,TDC...的定义
  • 原文地址:https://blog.csdn.net/yirenyuan/article/details/128156391