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