4.10 线程状态转换(Java API)
所附代码为转换方式部分(非全部)展示
4.10.1 NEW - - - > RUNNABLE
线程可调用 start 方法使其状态实现从 NEW 到 RUNNABLE 的转换
package com.rui.state;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
}, "t");
log.debug("此时 t 线程的状态为:{}", t.getState());
t.start();
log.debug("此时 t 线程的状态为:{}", t.getState());
}
}
// 运行结果
09:01:28 [main] c.Test1 - 此时 t 线程的状态为:NEW
09:01:28 [main] c.Test1 - 此时 t 线程的状态为:RUNNABLE
进程已结束,退出代码 0
4.10.2 RUNNABLE < - - - > WAITING
线程状态实现 RUNNABLE 和 WAITING 间的转换有三种方式
方式一
t 线程通过 synchronized (o) 获取对象锁后
通过 o.wait (); 语句使其状态实现从 RUNNABLE 到 WAITING 的转换
执行 o.notify(); / o.notifyAll(); / t.interrupt(); 语句,该线程会从 WaitSet 进入 EntryList,使其状态实现从 WAITING 到 BLOCKED 的转换
若该线程获取锁(竞争锁)成功,该线程的状态会从 BLOCKED 转换为 RUNNABLE;
若该线程获取锁失败,该线程会保持 BLOCKED 状态。
package com.rui.state;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) throws InterruptedException {
final Object o = new Object();
Thread t1 = new Thread(() -> {
synchronized (o) {
try {
log.debug("此时 t1 线程的状态:{}", Thread.currentThread().getState());
o.wait();
log.debug("此时 t1 线程的状态:{}", Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (o) {
try {
log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
o.wait();
log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t2");
t1.start();
t2.start();
Thread.sleep(500);
log.debug("此时 t1 线程的状态:{}", t1.getState());
log.debug("此时 t2 线程的状态:{}", t2.getState());
synchronized (o) {
o.notifyAll();
log.debug("此时 t1 线程的状态:{}", t1.getState());
log.debug("此时 t2 线程的状态:{}", t2.getState());
}
}
}
// 某次运行结果
15:47:26 [t1] c.Test5 - 此时 t1 线程的状态:RUNNABLE
15:47:26 [t2] c.Test5 - 此时 t2 线程的状态:RUNNABLE
15:47:27 [main] c.Test5 - 此时 t1 线程的状态:WAITING
15:47:27 [main] c.Test5 - 此时 t2 线程的状态:WAITING
15:47:27 [main] c.Test5 - 此时 t1 线程的状态:BLOCKED
15:47:27 [main] c.Test5 - 此时 t2 线程的状态:BLOCKED
15:47:27 [t2] c.Test5 - 此时 t2 线程的状态:RUNNABLE
15:47:27 [t1] c.Test5 - 此时 t1 线程的状态:RUNNABLE
进程已结束,退出代码 0
方式二
在 t2 线程中执行 t1.join (); 语句可使 t2 线程的状态实现从 RUNNABLE 到 WAITING 的转换
待 t1 线程运行结束或执行 t2.interrupt(); 语句,t2 线程的状态会从 WAITING 转换为 RUNNABLE
package com.rui.state;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test3")
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
log.debug("t1 线程开始运行...");
Thread.sleep(2000);
log.debug("t1 线程结束运行...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
Thread t2 = new Thread(() -> {
try {
log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
log.debug("join...");
t1.join();
log.debug("t1 线程运行结束,t2 线程开始运行");
log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2");
t1.start();
t2.start();
Thread.sleep(1000);
log.debug("此时 t2 线程的状态:{}", t2.getState());
}
}
// 某次运行结果
15:17:14 [t1] c.Test3 - t1 线程开始运行...
15:17:14 [main] c.Test3 - 此时 t2 线程的状态:RUNNABLE
15:17:15 [t2] c.Test3 - join...
15:17:15 [main] c.Test3 - 此时 t2 线程的状态:WAITING
15:17:16 [t1] c.Test3 - t1 线程结束运行...
15:17:16 [t2] c.Test3 - t1 线程运行结束,t2 线程开始运行
15:17:16 [t2] c.Test3 - 此时 t2 线程的状态:RUNNABLE
进程已结束,退出代码 0
方式三
在 t 线程中执行 LockSupport.park(); 语句可使该线程的状态实现从 RUNNABLE 到 WAITING 的转换
再执行 LockSupport.unpark(Thread t); / t.interrupt(); 语句可使 t 线程的状态实现从 WAITING 到 RUNNABLE 的转换
package com.rui.state;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
@Slf4j(topic = "c.Test4")
public class Test4 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
log.debug("此时 t 线程的状态:{}", Thread.currentThread().getState());
LockSupport.park();
log.debug("此时 t 线程的状态:{}", Thread.currentThread().getState());
});
t.start();
Thread.sleep(500);
log.debug("此时 t 线程的状态:{}", t.getState());
LockSupport.unpark(t);
}
}
// 某次运行结构
15:33:20 [Thread-0] c.Test4 - 此时 t 线程的状态:RUNNABLE
15:33:21 [main] c.Test4 - 此时 t 线程的状态:WAITING
15:33:21 [Thread-0] c.Test4 - 此时 t 线程的状态:RUNNABLE
进程已结束,退出代码 0
4.10.3 RUNNABLE < - - - > TIMED_WAITING
线程状态实现 RUNNABLE 和 TIMED_WAITING 间的转换有四种方式
方式一
t 线程通过 synchronized (o) 获取对象锁后
通过 o.wait (long timeout); 语句使其状态实现从 RUNNABLE 到 TIMED_WAITING 的转换
执行 o.notify(); / o.notifyAll(); / t.interrupt(); 语句或 t 线程等待 timeout 毫秒后,该线程会从 WaitSet 进入 EntryList,使其状态实现从 TIMED_WAITING 到 BLOCKED 的转换
若该线程获取锁(竞争锁)成功,该线程的状态会从 BLOCKED 转换为 RUNNABLE;
若该线程获取锁失败,该线程会保持 BLOCKED 状态。
package com.rui.state;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test5")
public class Test5 {
public static void main(String[] args) throws InterruptedException {
final Object o = new Object();
Thread t1 = new Thread(() -> {
synchronized (o) {
try {
log.debug("此时 t1 线程的状态:{}", Thread.currentThread().getState());
o.wait(1000);
log.debug("此时 t1 线程的状态:{}", Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (o) {
try {
log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
o.wait(1000);
log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t2");
t1.start();
t2.start();
Thread.sleep(500);
log.debug("此时 t1 线程的状态:{}", t1.getState());
log.debug("此时 t2 线程的状态:{}", t2.getState());
synchronized (o) {
o.notifyAll();
log.debug("此时 t1 线程的状态:{}", t1.getState());
log.debug("此时 t2 线程的状态:{}", t2.getState());
}
}
}
// 某次运行结果
15:50:53 [t1] c.Test5 - 此时 t1 线程的状态:RUNNABLE
15:50:53 [t2] c.Test5 - 此时 t2 线程的状态:RUNNABLE
15:50:54 [main] c.Test5 - 此时 t1 线程的状态:TIMED_WAITING
15:50:54 [main] c.Test5 - 此时 t2 线程的状态:TIMED_WAITING
15:50:54 [main] c.Test5 - 此时 t1 线程的状态:BLOCKED
15:50:54 [main] c.Test5 - 此时 t2 线程的状态:BLOCKED
15:50:54 [t2] c.Test5 - 此时 t2 线程的状态:RUNNABLE
15:50:54 [t1] c.Test5 - 此时 t1 线程的状态:RUNNABLE
进程已结束,退出代码 0
方式二
在 t2 线程中执行 t1.join (long millis); 语句可使 t2 线程的状态实现从 RUNNABLE 到 TIMED_WAITING 的转换
待 t1 线程运行结束或 t2 线程等待 millis 毫秒后或执行 t2.interrupt(); 语句,t2 线程的状态会从 TIMED_WAITING 转换为 RUNNABLE
package com.rui.state;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test6")
public class Test6 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
log.debug("t1 线程开始运行...");
Thread.sleep(2000);
log.debug("t1 线程结束运行...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
Thread t2 = new Thread(() -> {
try {
log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
log.debug("join...");
t1.join(3000);
log.debug("t1 线程运行结束,t2 线程开始运行");
log.debug("此时 t2 线程的状态:{}", Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2");
t1.start();
t2.start();
Thread.sleep(1000);
log.debug("此时 t2 线程的状态:{}", t2.getState());
}
}
// 某次运行结果
16:00:04 [t1] c.Test6 - t1 线程开始运行...
16:00:04 [t2] c.Test6 - 此时 t2 线程的状态:RUNNABLE
16:00:04 [t2] c.Test6 - join...
16:00:05 [main] c.Test6 - 此时 t2 线程的状态:TIMED_WAITING
16:00:06 [t1] c.Test6 - t1 线程结束运行...
16:00:06 [t2] c.Test6 - t1 线程运行结束,t2 线程开始运行
16:00:06 [t2] c.Test6 - 此时 t2 线程的状态:RUNNABLE
进程已结束,退出代码 0
方式三
在线程中执行 Thread.sleep(long millis); 语句可使该线程的状态实现从 RUNNABLE 到 TIMED_WAITING 的转换
待该线程等待 millis 毫秒后或执行 t2.interrupt(); 语句,t2 线程的状态会从 TIMED_WAITING 转换为 RUNNABLE
package com.rui.state;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test")
public class Test7 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
log.debug("此时 t 线程的状态:{}", Thread.currentThread().getState());
Thread.sleep(1000);
log.debug("此时 t 线程的状态:{}", Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t");
t.start();
Thread.sleep(500);
log.debug("此时 t 线程的状态:{}", t.getState());
}
}
// 某次运行结果
16:12:00 [t] c.Test - 此时 t 线程的状态:RUNNABLE
16:12:01 [main] c.Test - 此时 t 线程的状态:TIMED_WAITING
16:12:01 [t] c.Test - 此时 t 线程的状态:RUNNABLE
进程已结束,退出代码 0
方式四
对该方式有个印象即可
在 t 线程中执行 LockSupport.parkNanos(long nanos); / LockSupport.parkUntil(long deadline); 语句可使该线程的状态实现从 RUNNABLE 到 TIMED_WAITING 的转换
再执行 LockSupport.unpark(Thread t); / t.interrupt(); 语句或者等待超时后可使 t 线程的状态实现从 TIMED_WAITING 到 RUNNABLE 的转换
4.10.4 RUNNABLE < - - - > BLOCKED
多个线程同时获取同一把对象锁时(竞争锁):未拥有锁的线程进入 EntryList(竞争失败),其状态由 RUNNABLE 转换为 BLOCKED;待该对象锁被释放,EntryList 中的线程获取锁(竞争锁),若某个线程拥有锁(竞争成功),则其状态由 BLOCKED 转换为 RUNNABLE。
4.10.5 RUNNABLE - - - > TERMINATED
当线程运行结束时,其状态由 RUNNABLE 转换为 TERMINATED
4.11 多把锁
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test12")
public class Test12 {
public static void main(String[] args) {
Room room = new Room();
new Thread(() -> {
try {
room.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
new Thread(() -> {
try {
room.study();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "B").start();
}
}
@Slf4j(topic = "c.Room")
class Room {
public synchronized void study() throws InterruptedException {
log.debug("学习 1 小时...");
Thread.sleep(1000);
}
public synchronized void sleep() throws InterruptedException {
log.debug("睡眠 2 小时...");
Thread.sleep(2000);
}
}
// 某次运行结果
09:50:00 [A] c.Room - 睡眠 2 小时...
09:50:02 [B] c.Room - 学习 1 小时...
进程已结束,退出代码 0
若把 A、B 两个线程看做是两个同学,根据常识可知 A 同学睡眠与 B 同学学习并不冲突。
根据上述代码的某次运行结果可以看出,B 同学需要等待 A 同学睡眠结束才能开始学习,影响效率。
导致上述现象产生的原因是由于 A 同学睡眠与B 同学学习共用了同一把锁
因此,由上述比喻分析可以得出:当多个线程间执行的指令互不干扰时,若这些指令共用同一把锁会影响对应进程的运行效率
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test12")
public class Test12 {
public static void main(String[] args) {
Room room = new Room();
new Thread(() -> {
try {
room.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
new Thread(() -> {
try {
room.study();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "B").start();
}
}
@Slf4j(topic = "c.Room")
class Room {
private final Object studyRoom = new Object();
private final Object sleepRoom = new Object();
public void study() throws InterruptedException {
synchronized (studyRoom) {
log.debug("学习 1 小时...");
Thread.sleep(1000);
}
}
public void sleep() throws InterruptedException {
synchronized (sleepRoom) {
log.debug("睡眠 2 小时...");
Thread.sleep(2000);
}
}
}
// 某次运行结果
10:10:27 [A] c.Room - 睡眠 2 小时...
10:10:27 [B] c.Room - 学习 1 小时...
进程已结束,退出代码 0
根据上述代码的某次运行结果可以看出,A 线程与 B 线程同时运行。
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test12")
public class Test12 {
public static void main(String[] args) {
new Thread(() -> {
try {
new Room().sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
new Thread(() -> {
try {
new Room().study();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "B").start();
}
}
@Slf4j(topic = "c.Room")
class Room {
public synchronized void study() throws InterruptedException {
log.debug("学习 1 小时...");
Thread.sleep(1000);
}
public synchronized void sleep() throws InterruptedException {
log.debug("睡眠 2 小时...");
Thread.sleep(2000);
}
}
// 某次运行结果
10:10:27 [A] c.Room - 睡眠 2 小时...
10:10:27 [B] c.Room - 学习 1 小时...
进程已结束,退出代码 0
该思路用在此处也对,但不建议。因为若当多个线程间执行的指令一部分互不干扰,另一部分互相干扰时,阁下又该如何应对呢?
运用多把锁可能会带来死锁问题
4.12 活跃性
多线程的活跃性有三种:死锁、活锁、饥饿锁
4.12.1 死锁
t1 线程已拥有 A 对象锁,想要获取 B 对象锁;t2 线程已拥有 B 对象锁,想要获取 A 对象锁。
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test13")
public class Test13 {
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {
synchronized (A) {
log.debug("拥有 A 对象锁");
synchronized (B) {
log.debug("拥有 B 对象锁");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (B) {
log.debug("拥有 B 对象锁");
synchronized (A) {
log.debug("拥有 A 对象锁");
}
}
}, "t2");
t1.start();
t2.start();
}
}
// 某次运行结果
16:44:50 [Thread-0] c.Test13 - 拥有 A 对象锁
16:44:50 [Thread-1] c.Test13 - 拥有 B 对象锁
哲学家就餐问题
有五位哲学家,围坐在圆桌旁。
他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
如果筷子被身边的人拿着,自己就得等待。
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test14")
public class Test14 {
public static void main(String[] args) {
Chopsticks c1 = new Chopsticks("1");
Chopsticks c2 = new Chopsticks("2");
Chopsticks c3 = new Chopsticks("3");
Chopsticks c4 = new Chopsticks("4");
Chopsticks c5 = new Chopsticks("5");
new Thinker("A", c1, c2).start();
new Thinker("B", c2, c3).start();
new Thinker("C", c3, c4).start();
new Thinker("D", c4, c5).start();
new Thinker("E", c5, c1).start();
}
}
@Slf4j(topic = "c.Thinker")
class Thinker extends Thread {
private Chopsticks left;
private Chopsticks right;
public Thinker(String name, Chopsticks left, Chopsticks right) {
super(name);
this.left = left;
this.right = right;
}
public void action() throws InterruptedException {
log.debug("吃饭...");
Thread.sleep(1000); // 思考...
}
@Override
public void run() {
while (true) {
synchronized (left) {
synchronized (right) {
try {
action();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class Chopsticks {
private String name;
public Chopsticks(String name) {
this.name = name;
}
@Override
public String toString() {
return "Chopsticks{" +
"name='" + name + '\'' +
'}';
}
}
// 某次运行结果
19:37:10 [A] c.Thinker - 吃饭...
19:37:10 [C] c.Thinker - 吃饭...
19:37:11 [C] c.Thinker - 吃饭...
19:37:12 [B] c.Thinker - 吃饭...
4.12.2 活锁
两个线程互相改变对方的结束条件,导致线程无法运行结束。
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test18")
public class Test18 {
static volatile int count = 10;
public static void main(String[] args) {
new Thread(() -> {
while (count > 0) {
try {
Thread.sleep(200);
count--;
log.debug("t1 线程:count = {}", count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
new Thread(() -> {
while (count < 20) {
try {
Thread.sleep(200);
count++;
log.debug("t2 线程:count = {}", count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t2").start();
}
}
// 某次运行结果
22:17:19 [t1] c.Test18 - t1 线程:count = 11
22:17:19 [t2] c.Test18 - t2 线程:count = 11
22:17:20 [t1] c.Test18 - t1 线程:count = 10
22:17:20 [t2] c.Test18 - t2 线程:count = 11
22:17:20 [t1] c.Test18 - t1 线程:count = 10
22:17:20 [t2] c.Test18 - t2 线程:count = 11
22:17:20 [t1] c.Test18 - t1 线程:count = 10
22:17:20 [t2] c.Test18 - t2 线程:count = 11
22:17:20 [t1] c.Test18 - t1 线程:count = 10
22:17:20 [t2] c.Test18 - t2 线程:count = 11
22:17:20 [t1] c.Test18 - t1 线程:count = 10
22:17:20 [t2] c.Test18 - t2 线程:count = 11
22:17:21 [t1] c.Test18 - t1 线程:count = 10
22:17:21 [t2] c.Test18 - t2 线程:count = 11
22:17:21 [t1] c.Test18 - t1 线程:count = 10
22:17:21 [t2] c.Test18 - t2 线程:count = 11
22:17:21 [t1] c.Test18 - t1 线程:count = 10
22:17:21 [t2] c.Test18 - t2 线程:count = 11
22:17:21 [t1] c.Test18 - t1 线程:count = 10
22:17:21 [t2] c.Test18 - t2 线程:count = 11
22:17:21 [t1] c.Test18 - t1 线程:count = 10
22:17:21 [t2] c.Test18 - t2 线程:count = 11
22:17:22 [t1] c.Test18 - t1 线程:count = 10
22:17:22 [t2] c.Test18 - t2 线程:count = 11
22:17:22 [t1] c.Test18 - t1 线程:count = 10
22:17:22 [t2] c.Test18 - t2 线程:count = 11
22:17:22 [t1] c.Test18 - t1 线程:count = 10
22:17:22 [t2] c.Test18 - t2 线程:count = 11
22:17:22 [t1] c.Test18 - t1 线程:count = 10
22:17:22 [t2] c.Test18 - t2 线程:count = 11
22:17:22 [t1] c.Test18 - t1 线程:count = 10
22:17:22 [t2] c.Test18 - t2 线程:count = 11
22:17:23 [t1] c.Test18 - t1 线程:count = 10
22:17:23 [t2] c.Test18 - t2 线程:count = 11
22:17:23 [t1] c.Test18 - t1 线程:count = 10
22:17:23 [t2] c.Test18 - t2 线程:count = 11
22:17:23 [t1] c.Test18 - t1 线程:count = 10
22:17:23 [t2] c.Test18 - t2 线程:count = 11
22:17:23 [t1] c.Test18 - t1 线程:count = 10
22:17:23 [t2] c.Test18 - t2 线程:count = 11
22:17:23 [t1] c.Test18 - t1 线程:count = 10
22:17:23 [t2] c.Test18 - t2 线程:count = 11
22:17:24 [t1] c.Test18 - t1 线程:count = 10
22:17:24 [t2] c.Test18 - t2 线程:count = 11
22:17:24 [t1] c.Test18 - t1 线程:count = 10
22:17:24 [t2] c.Test18 - t2 线程:count = 11
22:17:24 [t1] c.Test18 - t1 线程:count = 10
22:17:24 [t2] c.Test18 - t2 线程:count = 11
22:17:24 [t1] c.Test18 - t1 线程:count = 10
22:17:24 [t2] c.Test18 - t2 线程:count = 11
22:17:24 [t1] c.Test18 - t1 线程:count = 10
22:17:25 [t2] c.Test18 - t2 线程:count = 11
22:17:25 [t1] c.Test18 - t1 线程:count = 10
// ...
4.12.3 饥饿锁
线程一直未拥有锁。
修改【哲学家就餐问题】中 main 函数的代码
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test19")
public class Test19 {
public static void main(String[] args) {
Chopsticks c1 = new Chopsticks("1");
Chopsticks c2 = new Chopsticks("2");
Chopsticks c3 = new Chopsticks("3");
Chopsticks c4 = new Chopsticks("4");
Chopsticks c5 = new Chopsticks("5");
new Thinker("A", c1, c2).start();
new Thinker("B", c2, c3).start();
new Thinker("C", c3, c4).start();
new Thinker("D", c4, c5).start();
new Thinker("E", c1, c5).start();
}
}
// 某次运行结果
22:27:48 [C] c.Thinker - 吃饭...
22:27:48 [A] c.Thinker - 吃饭...
22:27:49 [C] c.Thinker - 吃饭...
22:27:49 [A] c.Thinker - 吃饭...
22:27:50 [C] c.Thinker - 吃饭...
22:27:51 [C] c.Thinker - 吃饭...
22:27:52 [B] c.Thinker - 吃饭...
22:27:52 [D] c.Thinker - 吃饭...
22:27:53 [D] c.Thinker - 吃饭...
// ...
通过某次运行结果可以发现,E 线程一直未拥有锁。
E 线程的活跃性:饥饿锁
4.13 ReentrantLock
ReentrantLock 具有可打断、可设置超时时间、可设置为公平锁、支持多个条件变量和支持可重入的特点
// 语法
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// ...
} finally {
lock.unlock();
}
4.13.1 可打断
lockInterruptibly() - 可打断线程的阻塞状态
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test15")
public class Test15 {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t = new Thread(() -> {
try {
log.debug("t 线程获取锁");
lock.lockInterruptibly();
try {
log.debug("t 线程拥有锁");
} finally {
log.debug("t 线程释放锁");
lock.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("t 线程获取锁失败");
}
}, "t");
lock.lock();
t.start();
try {
log.debug("打断 t 线程");
t.interrupt();
} finally {
lock.unlock();
}
}
}
// 某次运行结果
09:29:17 [t] c.Test15 - t 线程获取锁
09:29:17 [main] c.Test15 - 打断 t 线程
09:29:17 [t] c.Test15 - t 线程获取锁失败
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at com.rui.blocked.Test15.lambda$main$0(Test15.java:14)
at java.lang.Thread.run(Thread.java:745)
进程已结束,退出代码 0
4.13.2 可设置超时时间
tryLock() - 若获取锁成功,拥有锁;若获取锁失败,放弃获取锁(即刻生效)
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test16")
public class Test16 {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t = new Thread(() -> {
log.debug("t 线程获取锁");
if (!lock.tryLock()) {
log.debug("t 线程获取锁失败");
return;
}
try {
log.debug("t 线程拥有锁");
} finally {
log.debug("t 线程释放锁");
lock.unlock();
}
}, "t1");
lock.lock();
t.start();
try {
log.debug("主线程运行状态:{}", Thread.currentThread().getState());
} finally {
lock.unlock();
}
}
}
// 某次运行结果
09:50:44 [t1] c.Test16 - t 线程获取锁
09:50:44 [main] c.Test16 - 主线程运行状态:RUNNABLE
09:50:44 [t1] c.Test16 - t 线程获取锁失败
进程已结束,退出代码 0
tryLock(long timeout, TimeUnit unit) - 在 timeout unit 时间内:若获取锁成功,拥有锁;若获取锁失败,放弃获取锁
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test17")
public class Test17 {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t = new Thread(() -> {
log.debug("t 线程获取锁");
try {
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
log.debug("t 线程获取锁失败");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("t 线程拥有锁");
} finally {
log.debug("t 线程释放锁");
lock.unlock();
}
}, "t1");
lock.lock();
t.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
// 某次运行结果
09:57:16 [t1] c.Test17 - t 线程获取锁
09:57:17 [t1] c.Test17 - t 线程获取锁失败
进程已结束,退出代码 0
解决【哲学家就餐问题】中的死锁现象
此处需注意:若获取锁失败则无需释放锁,故不要把判断获取锁是否成功的语句写在 try 代码块中
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test14")
public class Test14 {
public static void main(String[] args) {
Chopsticks c1 = new Chopsticks("1");
Chopsticks c2 = new Chopsticks("2");
Chopsticks c3 = new Chopsticks("3");
Chopsticks c4 = new Chopsticks("4");
Chopsticks c5 = new Chopsticks("5");
new Thinker("A", c1, c2).start();
new Thinker("B", c2, c3).start();
new Thinker("C", c3, c4).start();
new Thinker("D", c4, c5).start();
new Thinker("E", c5, c1).start();
}
}
@Slf4j(topic = "c.Thinker")
class Thinker extends Thread {
private Chopsticks left;
private Chopsticks right;
public Thinker(String name, Chopsticks left, Chopsticks right) {
super(name);
this.left = left;
this.right = right;
}
public void action() throws InterruptedException {
log.debug("吃饭...");
Thread.sleep(1000); // 思考...
}
@Override
public void run() {
while (true) {
if (left.tryLock()) {
try {
if (right.tryLock()) {
try {
action();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
right.unlock();
}
}
} finally {
left.unlock();
}
}
}
}
}
class Chopsticks extends ReentrantLock {
private String name;
public Chopsticks(String name) {
this.name = name;
}
@Override
public String toString() {
return "Chopsticks{" +
"name='" + name + '\'' +
'}';
}
}
// 某次运行结果
10:16:03 [A] c.Thinker - 吃饭...
10:16:03 [C] c.Thinker - 吃饭...
10:16:04 [B] c.Thinker - 吃饭...
10:16:04 [D] c.Thinker - 吃饭...
10:16:05 [B] c.Thinker - 吃饭...
10:16:05 [E] c.Thinker - 吃饭...
10:16:06 [C] c.Thinker - 吃饭...
10:16:06 [E] c.Thinker - 吃饭...
10:16:07 [B] c.Thinker - 吃饭...
10:16:07 [E] c.Thinker - 吃饭...
10:16:08 [E] c.Thinker - 吃饭...
10:16:08 [B] c.Thinker - 吃饭...
10:16:09 [D] c.Thinker - 吃饭...
10:16:09 [A] c.Thinker - 吃饭...
10:16:10 [D] c.Thinker - 吃饭...
10:16:10 [A] c.Thinker - 吃饭...
10:16:11 [E] c.Thinker - 吃饭...
10:16:11 [B] c.Thinker - 吃饭...
10:16:12 [E] c.Thinker - 吃饭...
// ...
4.13.3 可设置为公平锁
4.13.4 支持多个条件变量
await() / await(long time, TimeUnit unit) / signal() / singnalAll()
类似于 wait() / wait(long timeout) / notify() / notifyAll()
要点:
1. 调用 await() / await(long time, TimeUnit unit) 前需要获取锁
2. 调用 await() / await(long time, TimeUnit unit) 后线程会进入 conditionObject 等待,并释放锁
3. conditionObject 中的线程被唤醒后需重新获取锁
类似于 wait() / wait(long timeout)
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test20")
public class Test20 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteSet = lock.newCondition();
static Condition waitTakeoutSet = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
lock.lock();
try {
while (!hasCigarette) {
waitCigaretteSet.await();
}
log.debug("begin...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "小南").start();
new Thread(() -> {
lock.lock();
try {
while (!hasTakeout) {
waitTakeoutSet.await();
}
log.debug("begin...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "小女").start();
Thread.sleep(1000);
new Thread(() -> {
lock.lock();
try {
hasTakeout = true;
waitTakeoutSet.signal();
} finally {
lock.unlock();
}
}, "送外卖的").start();
new Thread(() -> {
lock.lock();
try {
hasCigarette = true;
waitCigaretteSet.signal();
} finally {
lock.unlock();
}
}, "送烟的").start();
}
}
// 某次运行结果
10:35:15 [小女] c.Test20 - begin...
10:35:15 [小南] c.Test20 - begin...
进程已结束,退出代码 0
4.13.5 支持可重入
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test21")
public class Test21 {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock();
try {
log.debug("main begin...");
m1();
} finally {
lock.unlock();
}
}
public static void m1() {
lock.lock();
try {
log.debug("m1 begin...");
m2();
} finally {
lock.unlock();
}
}
public static void m2() {
lock.lock();
try {
log.debug("m2 begin...");
} finally {
lock.unlock();
}
}
}
// 某次运行结果
10:47:40 [main] c.Test21 - main begin...
10:47:40 [main] c.Test21 - m1 begin...
10:47:40 [main] c.Test21 - m2 begin...
进程已结束,退出代码 0
4.14 顺序控制
4.14.1 固定运行顺序
new Thread(() -> {
System.out.println(1);
}).start();
new Thread(() -> {
System.out.println(2);
}).start();
// 需求:输出 21
wait / notify
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test22")
public class Test22 {
static Object o = new Object();
static Boolean flag = false;
public static void main(String[] args) {
new Thread(() -> {
synchronized (o) {
while (!flag) {
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(1);
}
}).start();
new Thread(() -> {
synchronized (o) {
System.out.print(2);
flag = true;
o.notify();
}
}).start();
}
}
// 运行结果
21
进程已结束,退出代码 0
park / unpark
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
@Slf4j(topic = "c.Test23")
public class Test23 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
LockSupport.park();
System.out.print(1);
});
t.start();
new Thread(() -> {
System.out.print(2);
LockSupport.unpark(t);
}).start();
}
}
// 运行结果
21
进程已结束,退出代码 0
4.14.2 交替输出
需求:线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现
wait / notify
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test24")
public class Test24 {
public static void main(String[] args) {
WaitNotify wn = new WaitNotify(1, 5);
new Thread(() -> {
try {
wn.print(1, 2, "a");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
try {
wn.print(2, 3, "b");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2").start();
new Thread(() -> {
try {
wn.print(3, 1, "c");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t3").start();
}
}
class WaitNotify {
private int flag;
private int cycle;
public WaitNotify(int flag, int cycle) {
this.flag = flag;
this.cycle = cycle;
}
public void print(int currentFlag, int nextFlag, String str) throws InterruptedException {
for (int i = 0; i < cycle; i++) {
synchronized (this) {
while (currentFlag != flag) {
wait();
}
System.out.print(str);
flag = nextFlag;
notifyAll();
}
}
}
}
// 运行结果
abcabcabcabcabc
进程已结束,退出代码 0
await / signal
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test25")
public class Test25 {
public static void main(String[] args) throws InterruptedException {
AwaitSignal as = new AwaitSignal(5);
Condition t1 = as.newCondition();
Condition t2 = as.newCondition();
Condition t3 = as.newCondition();
new Thread(() -> {
try {
as.print(t1, t2, "a");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
try {
as.print(t2, t3, "b");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2").start();
new Thread(() -> {
try {
as.print(t3, t1, "c");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t3").start();
Thread.sleep(1000);
as.lock();
try {
t1.signal();
} finally {
as.unlock();
}
}
}
class AwaitSignal extends ReentrantLock {
private int cycle;
public AwaitSignal(int cycle) {
this.cycle = cycle;
}
public void print(Condition current, Condition next, String str) throws InterruptedException {
for (int i = 0; i < cycle; i++) {
lock();
try {
current.await();
System.out.print(str);
next.signal();
} finally {
unlock();
}
}
}
}
// 运行结果
abcabcabcabcabc
进程已结束,退出代码 0
park / unpark
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
@Slf4j(topic = "c.Test26")
public class Test26 {
static Thread t1;
static Thread t2;
static Thread t3;
public static void main(String[] args) throws InterruptedException {
ParkUnpark pu = new ParkUnpark(5);
t1 = new Thread(() -> {
pu.print(t2, "a");
}, "t1");
t2 = new Thread(() -> {
pu.print(t3, "b");
}, "t2");
t3 = new Thread(() -> {
pu.print(t1, "c");
}, "t3");
t1.start();
t2.start();
t3.start();
Thread.sleep(1000);
LockSupport.unpark(t1);
}
}
class ParkUnpark {
private int cycle;
public ParkUnpark(int cycle) {
this.cycle = cycle;
}
public void print(Thread thread, String str) {
for (int i = 0; i < cycle; i++) {
LockSupport.park();
System.out.print(str);
LockSupport.unpark(thread);
}
}
}
// 运行结果
abcabcabcabcabc
进程已结束,退出代码 0
说些废话
本篇文章为博主日常学习记录,故而会小概率存在各种错误,若您在浏览过程中发现一些,请在评论区指正,望我们共同进步,谢谢!