• 并发之多把锁和活跃性


    1 多把锁

    1 多把不相干锁

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

    解决办法: 准备多个房间(多个对象锁)

    class BigRoom {
         public void sleep() {
         synchronized (this) {
         log.debug("sleeping 2 小时");
         Sleeper.sleep(2);
         }
         }
         public void study() {
         synchronized (this) {
         log.debug("study 1 小时");
         Sleeper.sleep(1);
         }
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    BigRoom bigRoom = new BigRoom();
    new Thread(() -> {
     bigRoom.compute();
    },"小明").start();
    new Thread(() -> {
     bigRoom.sleep();
    },"小红").start();
    /*
    运行结果:
    12:13:54.471 [小明] c.BigRoom - study 1 小时
    12:13:55.476 [小红] c.BigRoom - sleeping 2 小时
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    优化

    class BigRoom {
     private final Object studyRoom = new Object();
     private final Object bedRoom = new Object();
         public void sleep() {
             synchronized (bedRoom) {
             log.debug("sleeping 2 小时");
             Sleeper.sleep(2);
             }
         }
         public void study() {
             synchronized (studyRoom) {
             log.debug("study 1 小时");
             Sleeper.sleep(1);
             }
         }
    }
    /*
    运行结果:
    12:15:35.069 [小明] c.BigRoom - study 1 小时
    12:15:35.069 [小红] c.BigRoom - sleeping 2 小时
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    说明,将锁的粒度细分, 好处是可以增加并发度, 坏处是如果一个线程需要获取多把锁,容易造成死锁

    2 活跃性

    1 死锁

    有下列情况:一个线程需要同时获取多把锁,这时就容易发生死锁:

    t1 线程 获得 A对象 锁,接下来想获取 B对象 的锁 t2 线程 获得 B对象 锁,接下来想获取 A对象 的锁.

    Object A = new Object();
    Object B = new Object();
        Thread t1 = new Thread(() -> {
             synchronized (A) {
             log.debug("lock A");
             sleep(1);
                 synchronized (B) {
                 log.debug("lock B");
                 log.debug("操作...");
                 }
             }
        }, "t1");
        Thread t2 = new Thread(() -> {
             synchronized (B) {
             log.debug("lock B");
             sleep(0.5);
                 synchronized (A) {
                 log.debug("lock A");
                 log.debug("操作...");
                 }
             }
        }, "t2");
    t1.start();
    t2.start();
    /*
    运行结果:
    12:22:06.962 [t2] c.TestDeadLock - lock B 
    12:22:06.962 [t1] c.TestDeadLock - lock A 
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    2 定位死锁

    检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁:

    步骤1

    cmd > jps
    Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
    12320 Jps
    22816 KotlinCompileDaemon
    33200 TestDeadLock // JVM 进程
    11508 Main
    28468 Launcher
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    步骤2

    cmd > jstack 33200
    
    ...
    
    Found one Java-level deadlock:
    =============================
    "Thread-1":
     waiting to lock monitor 0x000000000361d378 (object 0x000000076b5bf1c0, a java.lang.Object),
     which is held by "Thread-0"
    "Thread-0":
     waiting to lock monitor 0x000000000361e768 (object 0x000000076b5bf1d0, a java.lang.Object),
     which is held by "Thread-1"
    Java stack information for the threads listed above:
    ===================================================
    "Thread-1":
     at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28)
     - waiting to lock <0x000000076b5bf1c0> (a java.lang.Object)
     - locked <0x000000076b5bf1d0> (a java.lang.Object)
     at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source)
     at java.lang.Thread.run(Thread.java:745)
    "Thread-0":
     at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15)
     - waiting to lock <0x000000076b5bf1d0> (a java.lang.Object)
     - locked <0x000000076b5bf1c0> (a java.lang.Object)
     at thread.TestDeadLock$$Lambda$1/495053715.run(Unknown Source)
     at java.lang.Thread.run(Thread.java:745)
    Found 1 deadlock.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    说明:

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

    哲学家就餐问题
    在这里插入图片描述

    有五位哲学家,围坐在圆桌旁。

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

    筷子类

    class Chopstick {
     String name;
     public Chopstick(String name) {
     this.name = name;
     }
     @Override
     public String toString() {
     return "筷子{" + name + '}';
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    哲学家类

    class Philosopher extends Thread {
     Chopstick left;
     Chopstick right;
        public Philosopher(String name, Chopstick left, Chopstick right) {
         super(name);
         this.left = left;
         this.right = right;
         }
         private void eat() {
         log.debug("eating...");
         Sleeper.sleep(1);
         }
     
     @Override
     public void run() {
     while (true) {
     // 获得左手筷子
     synchronized (left) {
     // 获得右手筷子
     synchronized (right) {
     // 吃饭
     eat();
     }
     // 放下右手筷子
     }
     // 放下左手筷子
     }
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    吃饭

    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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    执行一下,就不下去

    12:33:15.575 [苏格拉底] c.Philosopher - eating... 
    12:33:15.575 [亚里士多德] c.Philosopher - eating... 
    12:33:16.580 [阿基米德] c.Philosopher - eating... 
    12:33:17.580 [阿基米德] c.Philosopher - eating... 
    // 运行停止, 不向下运行
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用 jconsole 检测死锁

    -------------------------------------------------------------------------
    名称: 阿基米德
    状态: cn.cf.Chopstick@1540e19d (筷子1) 上的BLOCKED, 拥有者: 苏格拉底
    总阻止数: 2, 总等待数: 1
    堆栈跟踪:
    cn.cf.Philosopher.run(TestDinner.java:48)
     - 已锁定 cn.cf.Chopstick@6d6f6e28 (筷子5)
    -------------------------------------------------------------------------
    名称: 苏格拉底
    状态: cn.cf.Chopstick@677327b6 (筷子2) 上的BLOCKED, 拥有者: 柏拉图
    总阻止数: 2, 总等待数: 1
    堆栈跟踪:
    cn.cf.Philosopher.run(TestDinner.java:48)
     - 已锁定 cn.cf.Chopstick@1540e19d (筷子1)
    -------------------------------------------------------------------------
    名称: 柏拉图
    状态: cn.cf.Chopstick@14ae5a5 (筷子3) 上的BLOCKED, 拥有者: 亚里士多德
    总阻止数: 2, 总等待数: 0
    堆栈跟踪:
    cn.cf.Philosopher.run(TestDinner.java:48)
     - 已锁定 cn.cf.Chopstick@677327b6 (筷子2)
    -------------------------------------------------------------------------
    名称: 亚里士多德
    状态: cn.cf.Chopstick@7f31245a (筷子4) 上的BLOCKED, 拥有者: 赫拉克利特
    总阻止数: 1, 总等待数: 1
    堆栈跟踪:
    cn.cf.Philosopher.run(TestDinner.java:48)
     - 已锁定 cn.cf.Chopstick@14ae5a5 (筷子3)
    -------------------------------------------------------------------------
    名称: 赫拉克利特
    状态: cn.cf.Chopstick@6d6f6e28 (筷子5) 上的BLOCKED, 拥有者: 阿基米德
    总阻止数: 2, 总等待数: 0
    堆栈跟踪:
    cn.cf.Philosopher.run(TestDinner.java:48)
     - 已锁定 cn.cf.Chopstick@7f31245a (筷子4)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    线程没有按预期结束, 暂停执行, 归类为活跃性问题,即 死锁,活锁和饥饿.

    3 活锁

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

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

    4 饥饿

    常见的理解是,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束.

    案例

    使用顺序加锁的方式解决之前的死锁问题.

    在这里插入图片描述

    线程1和线程2, 都要同时去获取对象A和对象B锁, 结果线程1获取到对象A锁, 线程2获取到对象B锁, 互相再向下进行, 就互相尝试获取锁,发生死锁.

    顺序加锁的解决方案

    在这里插入图片描述

    规定加锁的顺序, 当线程1和线程2,同时竞争对象锁A, 仅有一个线程成功后,才能去竞争对象锁B,不然就阻塞,此时可以解决死锁问题.

  • 相关阅读:
    未来十年将是Web3.0发展的黄金十年
    宝塔手动安装php扩展问题,php-m (php-cli)找不到扩展 php-fpm没问题
    华为公司 java 面试题
    【应用】布隆过滤器
    【HTTP】GET 和 POST 的区别
    3D 生成重建005-NeRF席卷3D的表达形式
    npm彻底清理缓存
    分页&日志
    Java Character.SubSet equals()方法具有什么功能呢?
    Python之Django
  • 原文地址:https://blog.csdn.net/ABestRookie/article/details/126023069