7.多把锁

高并发之多把锁

场景:一个房间,睡觉和学习两件事独立,互不干扰,现在小明想学习,小红想睡觉,如果是一个锁,并发效率低

@Slf4j(topic = "c.MoreLockTest")
public class MoreLockTest {

    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();
        new Thread(()-> {
            bigRoom.study();
        }, "小明").start();
        new Thread(()-> {
            bigRoom.sleep();
        }, "小红").start();
    }
}

@Slf4j(topic = "c.BigRoom")
class BigRoom{

    private final Object lock = new Object();

    public void study(){
        synchronized (lock) {
            log.debug("start study......");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.debug("end study......");
        }
    }

    public void sleep(){
        synchronized (lock) {
            log.debug("start sleep......");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.debug("end sleep......");
        }
    }

}

输出:

11:13:52.336 c.BigRoom [小明] - start study......
11:13:54.343 c.BigRoom [小明] - end study......
11:13:54.343 c.BigRoom [小红] - start sleep......
11:13:55.347 c.BigRoom [小红] - end sleep......

Process finished with exit code 0

采用两把锁改造

@Slf4j(topic = "c.MoreLockTest")
public class MoreLockTest {

    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();
        new Thread(bigRoom::study, "小明").start();
        new Thread(bigRoom::sleep, "小红").start();
    }
}

@Slf4j(topic = "c.BigRoom")
class BigRoom{

    private final Object sleepLock = new Object();

    private final Object bedLock = new Object();

    public void study(){
        synchronized (sleepLock) {
            log.debug("start study......");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.debug("end study......");
        }
    }

    public void sleep(){
        synchronized (bedLock) {
            log.debug("start sleep......");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.debug("end sleep......");
        }
    }

}

输出:

11:16:04.207 c.BigRoom [小红] - start sleep......
11:16:04.207 c.BigRoom [小明] - start study......
11:16:05.212 c.BigRoom [小红] - end sleep......
11:16:06.212 c.BigRoom [小明] - end study......

Process finished with exit code 0

==将锁粒度细分,可以提高并发,但是如果一个线程需要同时获取多把锁,容易造成死锁==

活跃性

线程内的代码是有限的,但是由于某种原因,导致线程一直执行不完的情况称为活跃性

造成活跃性的情况有三种,死锁活锁饥饿

死锁

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

@Slf4j(topic = "c.DeadlockTest")
public class DeadlockTest {

    public static void main(String[] args) {
        Object lockA = new Object();
        Object lockB = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (lockA){
                log.debug("get lockA ");
                SleepUtil.sleep(1);
                synchronized (lockB){
                    log.debug("get lockB...");
                    SleepUtil.sleep(1);
                }
                log.debug("t1 end...");
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (lockB){
                log.debug("get lockB ");
                SleepUtil.sleep(1);
                synchronized (lockA){
                    log.debug("get lockA...");
                    SleepUtil.sleep(1);
                }
                log.debug("t2 end...");
            }
        }, "t2");

        t1.start();
        t2.start();
    }
}

输出:

11:27:34.247 c.DeadlockTest [t2] - get lockB 
11:27:34.247 c.DeadlockTest [t1] - get lockA 

定位死锁

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

jps 命令行

jps
36384 Jps
1445 Main
36061 Launcher
36078 Launcher
36062 Launcher
36079 DeadlockTest
jstack 36079 

输出:

2024-01-30 11:32:50
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.361-b09 mixed mode):

"Attach Listener" #14 daemon prio=9 os_prio=31 tid=0x00007fbf1b147800 nid=0x5107 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" #13 prio=5 os_prio=31 tid=0x00007fbf1c009000 nid=0x1403 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"t2" #12 prio=5 os_prio=31 tid=0x00007fbf1a098000 nid=0x5903 waiting for monitor entry [0x0000700001dcf000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at cn.com.wuhm.juc.three.n4.DeadlockTest.lambda$main$1(DeadlockTest.java:35)
        - waiting to lock <0x000000076b7f8ff8> (a java.lang.Object)
        - locked <0x000000076b7f9008> (a java.lang.Object)
        at cn.com.wuhm.juc.three.n4.DeadlockTest$$Lambda$2/787387795.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:750)

"t1" #11 prio=5 os_prio=31 tid=0x00007fbf1a097800 nid=0x5803 waiting for monitor entry [0x0000700001ccc000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at cn.com.wuhm.juc.three.n4.DeadlockTest.lambda$main$0(DeadlockTest.java:23)
        - waiting to lock <0x000000076b7f9008> (a java.lang.Object)
        - locked <0x000000076b7f8ff8> (a java.lang.Object)
        at cn.com.wuhm.juc.three.n4.DeadlockTest$$Lambda$1/729864207.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:750)

"Service Thread" #10 daemon prio=9 os_prio=31 tid=0x00007fbf1b017800 nid=0x7e03 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #9 daemon prio=9 os_prio=31 tid=0x00007fbf1c051800 nid=0x7f03 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #8 daemon prio=9 os_prio=31 tid=0x00007fbf1c050800 nid=0x4c03 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #7 daemon prio=9 os_prio=31 tid=0x00007fbf1b00d000 nid=0x4e03 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #6 daemon prio=9 os_prio=31 tid=0x00007fbf1c04f800 nid=0x4f03 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #5 daemon prio=5 os_prio=31 tid=0x00007fbf1b017000 nid=0x4903 runnable [0x00007000015b7000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x000000076ac8e0a8> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x000000076ac8e0a8> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:53)

"Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007fbf1b00e800 nid=0x4703 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007fbf1c031800 nid=0x3103 in Object.wait() [0x00007000012ab000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076ab08f08> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:150)
        - locked <0x000000076ab08f08> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:171)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:188)

"Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007fbf19011000 nid=0x3003 in Object.wait() [0x00007000011a8000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076ab06ba0> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x000000076ab06ba0> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=31 tid=0x00007fbf1a00a000 nid=0x3403 runnable 

"GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007fbf1900a000 nid=0x2303 runnable 

"GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007fbf1900a800 nid=0x2403 runnable 

"GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007fbf1c808800 nid=0x3f03 runnable 

"GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007fbf1a80b800 nid=0x3d03 runnable 

"GC task thread#4 (ParallelGC)" os_prio=31 tid=0x00007fbf1c008800 nid=0x3c03 runnable 

"GC task thread#5 (ParallelGC)" os_prio=31 tid=0x00007fbf1900b800 nid=0x3b03 runnable 

"GC task thread#6 (ParallelGC)" os_prio=31 tid=0x00007fbf1900c000 nid=0x2d03 runnable 

"GC task thread#7 (ParallelGC)" os_prio=31 tid=0x00007fbf1900c800 nid=0x3803 runnable 

"GC task thread#8 (ParallelGC)" os_prio=31 tid=0x00007fbf1900d000 nid=0x3603 runnable 

"GC task thread#9 (ParallelGC)" os_prio=31 tid=0x00007fbf1c809000 nid=0x3503 runnable 

"VM Periodic Task Thread" os_prio=31 tid=0x00007fbf1a852000 nid=0x5703 waiting on condition 

JNI global references: 319

Found one Java-level deadlock:
=============================
"t2":
  waiting to lock monitor 0x00007fbf2000b2d8 (object 0x000000076b7f8ff8, a java.lang.Object),
  which is held by "t1"
"t1":
  waiting to lock monitor 0x00007fbf2000d958 (object 0x000000076b7f9008, a java.lang.Object),
  which is held by "t2"

Java stack information for the threads listed above:
===================================================
"t2":
        at cn.com.wuhm.juc.three.n4.DeadlockTest.lambda$main$1(DeadlockTest.java:35)
        - waiting to lock <0x000000076b7f8ff8> (a java.lang.Object)
        - locked <0x000000076b7f9008> (a java.lang.Object)
        at cn.com.wuhm.juc.three.n4.DeadlockTest$$Lambda$2/787387795.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:750)
"t1":
        at cn.com.wuhm.juc.three.n4.DeadlockTest.lambda$main$0(DeadlockTest.java:23)
        - waiting to lock <0x000000076b7f9008> (a java.lang.Object)
        - locked <0x000000076b7f8ff8> (a java.lang.Object)
        at cn.com.wuhm.juc.three.n4.DeadlockTest$$Lambda$1/729864207.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:750)

Found 1 deadlock.

关注最后输出的内容即可:

Found one Java-level deadlock:
=============================
"t2":
  waiting to lock monitor 0x00007fbf2000b2d8 (object 0x000000076b7f8ff8, a java.lang.Object),
  which is held by "t1"
"t1":
  waiting to lock monitor 0x00007fbf2000d958 (object 0x000000076b7f9008, a java.lang.Object),
  which is held by "t2"

Java stack information for the threads listed above:
===================================================
"t2":
        at cn.com.wuhm.juc.three.n4.DeadlockTest.lambda$main$1(DeadlockTest.java:35)
        - waiting to lock <0x000000076b7f8ff8> (a java.lang.Object)
        - locked <0x000000076b7f9008> (a java.lang.Object)
        at cn.com.wuhm.juc.three.n4.DeadlockTest$$Lambda$2/787387795.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:750)
"t1":
        at cn.com.wuhm.juc.three.n4.DeadlockTest.lambda$main$0(DeadlockTest.java:23)
        - waiting to lock <0x000000076b7f9008> (a java.lang.Object)
        - locked <0x000000076b7f8ff8> (a java.lang.Object)
        at cn.com.wuhm.juc.three.n4.DeadlockTest$$Lambda$1/729864207.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:750)

Found 1 deadlock.

t2线程在 DeadlockTest.java:35 行,t1线程在 DeadlockTest.java:23行 发生了死锁

jconsole 工具

哲学家就餐问题

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

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

创建筷子类

public class Chopstick {
    private String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }

}

创建哲学家类

@Slf4j(topic = "c.Philosopher")
public class Philosopher extends Thread{

    private Chopstick left;

    private Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        // 线程名称
        super(name);
        this.left = left;
        this.right = right;
    }

    private void eat(){
        log.debug("eating......");
        SleepUtil.sleep(1);
    }

    @Override
    public void run() {
        while (true){
            // 获取左边筷子
            synchronized (left){
                // 获取右边筷子
                synchronized (right){
                    // 吃饭
                    eat();
                } // 放下右手筷子
            } // 放下左手筷子
        }
    }
}

测试

@Slf4j(topic = "c.PhilosopherTest")
public class PhilosopherTest {

    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();
    }
}

执行一段时间后控制台不再输出:

16:23:31.271 c.Philosopher [苏格拉底] - eating......
16:23:31.271 c.Philosopher [亚里士多德] - eating......
16:23:32.277 c.Philosopher [苏格拉底] - eating......
16:23:32.277 c.Philosopher [亚里士多德] - eating......
16:23:33.281 c.Philosopher [苏格拉底] - eating......
16:23:33.281 c.Philosopher [亚里士多德] - eating......
16:23:34.285 c.Philosopher [苏格拉底] - eating......
16:23:34.286 c.Philosopher [亚里士多德] - eating......
16:23:35.290 c.Philosopher [苏格拉底] - eating......
16:23:35.291 c.Philosopher [亚里士多德] - eating......
16:23:36.295 c.Philosopher [亚里士多德] - eating......
16:23:36.295 c.Philosopher [苏格拉底] - eating......
16:23:37.298 c.Philosopher [亚里士多德] - eating......
16:23:38.303 c.Philosopher [亚里士多德] - eating......
16:23:39.307 c.Philosopher [亚里士多德] - eating......
16:23:40.312 c.Philosopher [亚里士多德] - eating......
16:23:41.316 c.Philosopher [亚里士多德] - eating......

通过jconsole查看死锁线程后:

活锁

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

@Slf4j(topic = "c.TestLiveLock")
public class TestLiveLock {
    static volatile int count = 50;
    static final Object lock = new Object();
    
    public static void main(String[] args) {
        new Thread(() -> {
            // 期望减到 0 退出循环
            while (count > 0) {
                sleep(1);
                count--;
                log.debug("count: {}", count);
            }
        }, "t1").start();
        
        new Thread(() -> {
            // 期望超过 1000 退出循环
            while (count < 100) {
                sleep(1);
                count++;
                log.debug("count: {}", count);
            }
        }, "t2").start();
        
    }
}

解决方案:让两个线程的睡眠时间不一样

饥饿

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

演示:调整 new Philosopher("阿基米德", c5, c1).start(); 加锁的顺序

@Slf4j(topic = "c.PhilosopherTest")
public class PhilosopherTest {

    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("阿基米德", c1, c5).start();
    }
}

输出:

......
16:36:19.228 c.Philosopher [赫拉克利特] - eating......
16:36:20.230 c.Philosopher [赫拉克利特] - eating......
16:36:21.231 c.Philosopher [赫拉克利特] - eating......
16:36:22.235 c.Philosopher [赫拉克利特] - eating......
16:36:23.240 c.Philosopher [赫拉克利特] - eating......
16:36:24.245 c.Philosopher [赫拉克利特] - eating......
16:36:25.248 c.Philosopher [赫拉克利特] - eating......
16:36:26.252 c.Philosopher [赫拉克利特] - eating......
16:36:27.258 c.Philosopher [赫拉克利特] - eating......
........
16:37:00.383 c.Philosopher [赫拉克利特] - eating......
16:37:01.384 c.Philosopher [亚里士多德] - eating......
16:37:02.388 c.Philosopher [亚里士多德] - eating......
16:37:03.392 c.Philosopher [亚里士多德] - eating......
16:37:04.394 c.Philosopher [亚里士多德] - eating......
16:37:05.396 c.Philosopher [赫拉克利特] - eating......
16:37:06.400 c.Philosopher [赫拉克利特] - eating......
16:37:07.401 c.Philosopher [赫拉克利特] - eating......
16:37:08.406 c.Philosopher [赫拉克利特] - eating......
....
16:37:23.459 c.Philosopher [赫拉克利特] - eating......
16:37:24.463 c.Philosopher [赫拉克利特] - eating......
16:37:25.468 c.Philosopher [赫拉克利特] - eating......
16:37:26.472 c.Philosopher [赫拉克利特] - eating......
16:37:27.477 c.Philosopher [赫拉克利特] - eating......
16:37:28.478 c.Philosopher [赫拉克利特] - eating......
.....

不会产生死锁问题,但是,阿基米德或是苏格拉底线程一直得不到执行。这样的线程称为饥饿现象

0%