• (八) 共享模型之管程【活跃性】


    一、多把锁(P114)

    一间大屋子有两个功能:睡觉、学习,互不相干。
    现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低

    解决方法是准备多个房间(多个对象锁)

    1. public class TestMultiLock {
    2. public static void main(String[] args) {
    3. BigRoom bigRoom = new BigRoom();
    4. new Thread(() -> {
    5. bigRoom.study();
    6. },"小南").start();
    7. new Thread(() -> {
    8. bigRoom.sleep();
    9. },"小女").start();
    10. }
    11. }

    1. @Slf4j(topic = "c.BigRoom")
    2. public class BigRoom {
    3. public void sleep() throws InterruptedException {
    4. synchronized (this) {
    5. log.debug("sleeping 2 小时");
    6. Thread.sleep(2);
    7. }
    8. }
    9. public void study() throws InterruptedException {
    10. synchronized (this) {
    11. log.debug("study 1 小时");
    12. Thread.sleep(1);
    13. }
    14. }
    15. }
    改进
    1. @Slf4j(topic = "c.BigRoom")
    2. public class BigRoom {
    3. private final Object studyRoom = new Object();
    4. private final Object bedRoom = new Object();
    5. public void sleep() throws InterruptedException {
    6. synchronized (bedRoom) {
    7. log.debug("sleeping 2 小时");
    8. Thread.sleep(2000);
    9. }
    10. }
    11. public void study() throws InterruptedException {
    12. synchronized (studyRoom) {
    13. log.debug("study 1 小时");
    14. Thread.sleep(1000);
    15. }
    16. }
    17. }

    将锁的粒度细分:

    好处:增强并发度。

    坏处:如果一个线程需要同时获得多把锁,就容易发生死锁

    二、死锁

    一个线程需要同时获取多把锁,这时就容易发生死锁。

    【t1 线程】 获得 A对象 锁,接下来想获取 B对象的锁;

    【t2 线程】 获得 B对象 锁,接下来想获取 A对象的锁。

    1. @Slf4j(topic = "c.TestDeadLock")
    2. public class TestDeadLock {
    3. public static void main(String[] args) {
    4. test1();
    5. }
    6. private static void test1() {
    7. Object A = new Object();
    8. Object B = new Object();
    9. Thread t1 = new Thread(() -> {
    10. synchronized (A) {
    11. log.debug("lock A");
    12. sleep(1);
    13. synchronized (B) {
    14. log.debug("lock B");
    15. log.debug("操作...");
    16. }
    17. }
    18. }, "t1");
    19. Thread t2 = new Thread(() -> {
    20. synchronized (B) {
    21. log.debug("lock B");
    22. sleep(0.5);
    23. synchronized (A) {
    24. log.debug("lock A");
    25. log.debug("操作...");
    26. }
    27. }
    28. }, "t2");
    29. t1.start();
    30. t2.start();
    31. }
    32. }

    三、定位死锁

    检测死锁可以使用 jconsole工具,

    或者使用 jps 定位进程 id,再用 jstack 定位死锁:

      

    (1)避免死锁要注意加锁顺序
    (2)另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程,再利用 top - Hp 进程 id 来定位是哪个线程,最后再用 jstack 排查

    四、哲学家就餐问题

    有五位哲学家,围坐在圆桌旁。
    (1)他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
    (2)吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
    (3)如果筷子被身边的人拿着,自己就得等待

     

    1. public class TestDeadLock {
    2. 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("阿基米德", c1, c5).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. synchronized (left) {
    29. // 尝试获得右手筷子
    30. synchronized (right) {
    31. eat();
    32. }
    33. }
    34. }
    35. }
    36. Random random = new Random();
    37. private void eat() {
    38. log.debug("eating...");
    39. Sleeper.sleep(0.5);
    40. }
    41. }
    42. class Chopstick {
    43. String name;
    44. public Chopstick(String name) {
    45. this.name = name;
    46. }
    47. @Override
    48. public String toString() {
    49. return "筷子{" + name + '}';
    50. }
    51. }

    这种线程没有按预期结束,执行不下去的情况,归类为【 活跃性 】问题,除了死锁以外,还有活锁和饥饿者两种情况

     

    五、活锁

    活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束。

    1. @Slf4j(topic = "c.TestLiveLock")
    2. public class TestLiveLock {
    3. static volatile int count = 10;
    4. static final Object lock = new Object();
    5. public static void main(String[] args) {
    6. new Thread(() -> {
    7. // 期望减到 0 退出循环
    8. while (count > 0) {
    9. sleep(0.2);
    10. count--;
    11. log.debug("count: {}", count);
    12. }
    13. }, "t1").start();
    14. new Thread(() -> {
    15. // 期望超过 20 退出循环
    16. while (count < 20) {
    17. sleep(0.2);
    18. count++;
    19. log.debug("count: {}", count);
    20. }
    21. }, "t2").start();
    22. }
    23. }

    六、饥饿

    很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,又不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题。

    下面我讲一下我遇到的一个线程饥饿的例子,先来看看使用顺序加锁的方式解决之前的死锁问题

     

    顺序加锁可以解决死锁,但是容易造成饥饿。

  • 相关阅读:
    C++ 语言学习 day02 (linux ) delete 函数 面对对象的类,构造函数,析构函数
    element ui,node-sass 安装出错,绝对能解决
    别再用 System.currentTimeMillis 统计耗时了,太 Low,试试 Spring Boot 源码在用的 StopWatch吧,够优雅
    访问者模式:对象结构的元素处理
    SSLHandshakeException: Remote host closed connection during handshake异常处理
    <HarmonyOS第一课>运行Hello World——闯关习题及答案
    代码坏味道(一)
    配置Maven并使用IDEA新建一个简单的Springboot项目
    xinput1_4.dll丢失怎么修复?修复方法分享
    鲲山科技:引入和鲸 ModelWhale,实现量化策略的高效迭代
  • 原文地址:https://blog.csdn.net/yirenyuan/article/details/128156107