文章目录
1.Park & Unpark
1.1 介绍
// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象);
先park()再unpark()
/**
* @ClassName TestParkUnpark
* @author: shouanzh
* @Description TestParkUnpark
* @date 2022/3/10 20:38
*/
@Slf4j
public class TestParkUnpark {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() ->{
log.debug("start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("park...");
LockSupport.park(); // WAIT状态
log.debug("end...");
},"t1");
thread.start();
Thread.sleep(2000);
log.debug("unpark...");
LockSupport.unpark(thread);
}
}
2022-03-10 20:43:07 [t1] - start
2022-03-10 20:43:08 [t1] - park...
2022-03-10 20:43:09 [main] - unpark...
2022-03-10 20:43:09 [t1] - end...
Process finished with exit code 0
先unpark()再park()
/**
* @ClassName TestParkUnpark
* @author: shouanzh
* @Description TestParkUnpark
* @date 2022/3/10 20:38
*/
@Slf4j
public class TestParkUnpark {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() ->{
log.debug("start");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("park...");
LockSupport.park();
log.debug("end...");
},"t1");
thread.start();
Thread.sleep(1000);
log.debug("unpark...");
LockSupport.unpark(thread);
}
}
2022-03-10 20:46:39 [t1] - start
2022-03-10 20:46:40 [main] - unpark...
2022-03-10 20:46:41 [t1] - park...
2022-03-10 20:46:41 [t1] - end...
Process finished with exit code 0
1.2 与 Object 的 wait & notify相比
- wait, notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park, unpark不必
- park & unpark是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程, notifyAll 是唤醒所有等待线程,就不那么【精确】
- park & unpark可以先 unpark, 而 wait & notify 不能先 notify
1.3 Park & Unpark 原理
每个线程都有自己的一个 Parker 对象,由三部分组成 _counter, _cond和 _mutex
线程就像一个旅人, Parker 就像他随身携带的背包, _cond条件变量就好比背包中的帐篷。 _counter 就好比背包 中的备用干粮(0为耗尽,1为充足)
-
调用 park 就是要看需不需要停下来歇息
- 如果备用干粮耗尽,那么钻进帐篷歇息
- 如果备用干粮充足,那么不需停留,继续前进
-
调用 unpark,就好比令干粮充足
- 如果这时线程还在帐篷,就唤醒让他继续前进
- 如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进
- 因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮
先调用park()再调用unpark()
调用park()
1.当前线程调用 Unsafe.park() 方法
2.检查 _counter, 本情况为0, 这时, 获得_mutex 互斥锁(mutex对象有个等待队列 _cond)
3.线程进入 _cond 条件变量阻塞
4.设置_counter = 0 (没干粮了)
再调用unpark()
1.调用Unsafe.unpark(Thread_0)方法,设置_counter 为 1
2.唤醒 _cond 条件变量中的 Thread_0
3.Thread_0 恢复运行
4.设置 _counter 为 0
先调用unpark再调用park的过程
1.调用 Unsafe.unpark(Thread_0)方法,设置 _counter 为 1
2.当前线程调用 Unsafe.park() 方法
3.检查 _counter,本情况为 1,这时线程 无需阻塞,继续运行
4.设置 _counter 为 0
2.线程状态转换
3.多把锁
3.1 多把不相干的锁
- 一间大屋子有两个功能:睡觉、学习,互不相干。
- 现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
- 小南获得锁之后, 学完习之后, 小女才能进来睡觉。
- 解决方法是准备多个房间(多个对象锁)
@Slf4j(topic = "z.BigRoomTest")
public class BigRoomTest {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
new Thread(() -> {
try {
bigRoom.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "小南").start();
new Thread(() -> {
try {
bigRoom.study();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "小女").start();
}
}
@Slf4j(topic = "z.BigRoom")
class BigRoom {
public void sleep() throws InterruptedException {
synchronized (this) {
log.debug("sleeping 2 小时");
Thread.sleep(2000);
}
}
public void study() throws InterruptedException {
synchronized (this) {
log.debug("study 1 小时");
Thread.sleep(1000);
}
}
}
相当于串行执行, 因为锁对象是整个屋子, 所以并发性很低
2022-03-10 22:20:05 [小南] - sleeping 2 小时
2022-03-10 22:20:07 [小女] - study 1 小时
Process finished with exit code 0
解决方法是准备多个房间(多个对象锁)
@Slf4j(topic = "z.BigRoomTest")
public class BigRoomTest {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
new Thread(() -> {
try {
bigRoom.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "小南").start();
new Thread(() -> {
try {
bigRoom.study();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "小女").start();
}
}
@Slf4j(topic = "z.BigRoom")
class BigRoom {
private final Object sleepRoom = new Object();
private final Object studyRoom = new Object();
public void sleep() throws InterruptedException {
synchronized (sleepRoom) {
log.debug("sleeping 2 小时");
Thread.sleep(2000);
}
}
public void study() throws InterruptedException {
synchronized (studyRoom) {
log.debug("study 1 小时");
Thread.sleep(1000);
}
}
}
2022-03-10 22:26:34 [小南] - sleeping 2 小时
2022-03-10 22:26:34 [小女] - study 1 小时
Process finished with exit code 0
将锁的粒度细分
- 好处,是可以增强并发度
- 坏处,如果一个线程需要同时获得多把锁,就容易发生死锁
3.2 死锁
线程1获取A对象锁, 线程2获取B对象锁; 此时线程1又想获取B对象锁, 线程2又想获取A对象锁; 它们都等着对象释放锁, 此时就称为死锁
/**
* @ClassName TestDeadLock
* @author: shouanzh
* @Description jps -l jstack 进程id
* @date 2022/3/10 22:37
*/
public class TestDeadLock {
public static void main(String[] args) {
final Object A = new Object();
final Object B = new Object();
new Thread(()->{
synchronized (A) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
}
}
}).start();
new Thread(()->{
synchronized (B) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A) {
}
}
}).start();
}
}
3.3 定位死锁
检测死锁可以使用jconsole工具,或者使用jps定位进程id,再用jstack定位死锁:
➜ ~ jps
26417
26919 Jps
26459 RemoteMavenServer36
26828 Launcher
26829 TestDeadLock
➜ ~
➜ ~ jstack 26829
2022-03-10 22:43:21
Full thread dump OpenJDK 64-Bit Server VM (25.312-b07 mixed mode):
"Thread-1" #12 prio=5 os_prio=31 tid=0x0000000136059000 nid=0x5903 waiting for monitor entry [0x0000000170622000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.concurrent.parktest.TestDeadLock.lambda$main$1(TestDeadLock.java:37)
- waiting to lock <0x000000076ac3e828> (a java.lang.Object)
- locked <0x000000076ac3e838> (a java.lang.Object)
at com.concurrent.parktest.TestDeadLock$$Lambda$2/1149319664.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Thread-0" #11 prio=5 os_prio=31 tid=0x0000000136015000 nid=0x5803 waiting for monitor entry [0x0000000170416000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.concurrent.parktest.TestDeadLock.lambda$main$0(TestDeadLock.java:24)
- waiting to lock <0x000000076ac3e838> (a java.lang.Object)
- locked <0x000000076ac3e828> (a java.lang.Object)
at com.concurrent.parktest.TestDeadLock$$Lambda$1/1023892928.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
// 找到一个死锁
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x000000013505e820 (object 0x000000076ac3e828, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00000001350610b0 (object 0x000000076ac3e838, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.concurrent.parktest.TestDeadLock.lambda$main$1(TestDeadLock.java:37)
- waiting to lock <0x000000076ac3e828> (a java.lang.Object)
- locked <0x000000076ac3e838> (a java.lang.Object)
at com.concurrent.parktest.TestDeadLock$$Lambda$2/1149319664.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at com.concurrent.parktest.TestDeadLock.lambda$main$0(TestDeadLock.java:24)
- waiting to lock <0x000000076ac3e838> (a java.lang.Object)
- locked <0x000000076ac3e828> (a java.lang.Object)
at com.concurrent.parktest.TestDeadLock$$Lambda$1/1023892928.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
3.4 死锁举例 - 哲学家就餐问题
有五位哲学家,围坐在圆桌旁。
- 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
- 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子
- 如果筷子被身边的人拿着,自己就得等待
当每个哲学家即线程持有一根筷子时,他们都在等待另一个线程释放锁,因此造成了死锁。
/**
* Description: 使用synchronized加锁, 导致哲学家就餐问题,
* 死锁: 核心原因是因为synchronized的锁是不可打断的, 进入阻塞队列
* 需要一直等待别的线程释放锁
*
*/
@Slf4j(topic = "z.PhilosopherEat")
public class PhilosopherEat {
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();
}
}
// 哲学家
@Slf4j(topic = "z.Philosopher")
class Philosopher extends Thread {
final Chopstick left;
final Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
@SneakyThrows
@Override
public void run() {
while (true) {
// 尝试获取左手筷子
synchronized (left) {
// 尝试获取右手筷子
synchronized (right) {
eat();
}
}
}
}
// 吃饭
private void eat() throws InterruptedException {
log.debug("eating...");
Thread.sleep(500);
}
}
// 筷子
class Chopstick{
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
检测死锁
➜ ~ jps
26417
26994 Launcher
26995 PhilosopherEat
26459 RemoteMavenServer36
27037 Jps
➜ ~
➜ ~
➜ ~ jstack 26995
Found one Java-level deadlock:
=============================
"阿基米德":
waiting to lock monitor 0x000000014f8c3760 (object 0x000000076bae99a8, a com.concurrent.parktest.Chopstick),
which is held by "苏格拉底"
"苏格拉底":
waiting to lock monitor 0x000000012f0fa210 (object 0x000000076bae99b8, a com.concurrent.parktest.Chopstick),
which is held by "柏拉图"
"柏拉图":
waiting to lock monitor 0x000000012f0fa160 (object 0x000000076bae99c8, a com.concurrent.parktest.Chopstick),
which is held by "亚里士多德"
"亚里士多德":
waiting to lock monitor 0x000000012f0fce10 (object 0x000000076bae99d8, a com.concurrent.parktest.Chopstick),
which is held by "赫拉克利特"
"赫拉克利特":
waiting to lock monitor 0x000000012f0fcd60 (object 0x000000076bae99e8, a com.concurrent.parktest.Chopstick),
which is held by "阿基米德"
Java stack information for the threads listed above:
===================================================
"阿基米德":
at com.concurrent.parktest.Philosopher.run(PhilosopherEat.java:50)
- waiting to lock <0x000000076bae99a8> (a com.concurrent.parktest.Chopstick)
- locked <0x000000076bae99e8> (a com.concurrent.parktest.Chopstick)
"苏格拉底":
at com.concurrent.parktest.Philosopher.run(PhilosopherEat.java:50)
- waiting to lock <0x000000076bae99b8> (a com.concurrent.parktest.Chopstick)
- locked <0x000000076bae99a8> (a com.concurrent.parktest.Chopstick)
"柏拉图":
at com.concurrent.parktest.Philosopher.run(PhilosopherEat.java:50)
- waiting to lock <0x000000076bae99c8> (a com.concurrent.parktest.Chopstick)
- locked <0x000000076bae99b8> (a com.concurrent.parktest.Chopstick)
"亚里士多德":
at com.concurrent.parktest.Philosopher.run(PhilosopherEat.java:50)
- waiting to lock <0x000000076bae99d8> (a com.concurrent.parktest.Chopstick)
- locked <0x000000076bae99c8> (a com.concurrent.parktest.Chopstick)
"赫拉克利特":
at com.concurrent.parktest.Philosopher.run(PhilosopherEat.java:50)
- waiting to lock <0x000000076bae99e8> (a com.concurrent.parktest.Chopstick)
- locked <0x000000076bae99d8> (a com.concurrent.parktest.Chopstick)
Found 1 deadlock.
3.5 活锁
活锁出现在两个线程 互相改变对方的结束条件,谁也无法结束。
/**
* @ClassName TestLiveLock
* @author: shouanzh
* @Description 活锁出现在两个线程 互相改变对方的结束条件,谁也无法结束。
* @date 2022/3/10 23:13
*/
@Slf4j
public class TestLiveLock {
static volatile int count = 10;
static final Object OBJECT = new Object();
public static void main(String[] args) {
new Thread(() -> {
while (count > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
log.debug("count:{}", count);
}
}, "t1").start();
new Thread(() -> {
while (count < 20) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
log.debug("count:{}", count);
}
}, "t2").start();
}
}
避免活锁的方法
- 在线程执行时,中途给予 不同的间隔时间, 让某个线程先结束即可。
死锁与活锁的区别
- 死锁是因为线程互相持有对象想要的锁,并且都不释放,最后到时线程阻塞,停止运行的现象。
- 活锁是因为线程间修改了对方的结束条件,而导致代码一直在运行,却一直运行不完的现象。
3.6 饥饿
一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束
3.7 避免死锁的方法
死锁
顺序加锁
@Slf4j(topic = "z.PhilosopherEat")
public class PhilosopherEat {
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(); // 饥饿
}
}
// 哲学家
@Slf4j(topic = "z.Philosopher")
class Philosopher extends Thread {
final Chopstick left;
final Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
@SneakyThrows
@Override
public void run() {
while (true) {
// 尝试获取左手筷子
synchronized (left) {
// 尝试获取右手筷子
synchronized (right) {
eat();
}
}
}
}
// 吃饭
private void eat() throws InterruptedException {
log.debug("eating...");
Thread.sleep(500);
}
}
// 筷子
class Chopstick{
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}