Appearance
1. 固定运行顺序
假设现在有两个线程,一个线程t1打印“hello world”,一个线程t2打印"12345",要求打印数字的线程先执行。
1.1 使用wait/notify实现
代码实现如下:
- t1线程检测运行标志
execFlag,当该标志为true时才接着运行,否则调用wait()方法放弃锁等待运行; - t2线程获取锁后执行任务,执行结束前将t1线程的运行标志
execFlag设置为true,并且调用notifyAll()唤醒t1线程;
java
private static Object lock = new Object();
private static boolean execFlag = false;
public static void main(String[] args) {
new Thread(()->{
synchronized (lock){
while (!execFlag){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.info("hello world");
}
},"t1").start();
new Thread(()->{
synchronized (lock){
log.info("12345");
execFlag = true;
lock.notifyAll();
}
},"t2").start();
}1.2 使用park/unpark实现
t1线程拿到锁后调用park()方法先暂停执行,直到t2线程运行结束前,调用unpark()唤醒t1线程才接着执行:
java
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
LockSupport.park();
log.info("hello world");
}, "t1");
t1.start();
new Thread(()->{
log.info("12345");
LockSupport.unpark(t1);
},"t2").start();
}2. 轮流执行
要求t1线程输出a,t2线程输出b,t3线程输出c,按照abc的顺序执行5次。
2.1 使用wait/notify实现
使用标志flag来指示哪个线程该运行,某个线程运行完成后,修改标志值,并且唤醒下一个线程:
java
public class Demo {
public static void main(String[] args) {
Task task = new Task();
new Thread(() -> {
task.run(5, 1, 2, "a");
}, "t1").start();
new Thread(() -> {
task.run(5, 2, 3, "b");
}, "t2").start();
new Thread(() -> {
task.run(5, 3, 1, "c");
}, "t3").start();
}
}
class Task{
private static Object lock = new Object();
// 1 - t1线程执行,2 - t2线程执行,3 - t3线程执行
private static int flag = 1;
/**
* 循环指定次数,输出指定内容,并唤醒下一个线程开始执行
* @param loopNum 循环次数
* @param expectFlag 期望的标志值
* @param nextFlag 标志下一个值
* @param content 输出内容
*/
public void run(int loopNum, int expectFlag, int nextFlag, String content){
for (int i = 0; i < loopNum; i++) {
synchronized (lock){
while (flag != expectFlag){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.print(content);
flag = nextFlag;
lock.notifyAll();
}
}
}
}2.2 使用await/signal实现
由于ReentrantLock支持多个条件变量,所以可以让不同的线程进入不同的条件变量,做到精确唤醒:
java
public class Demo {
public static void main(String[] args) {
TaskReentrantLock task = new TaskReentrantLock();
new Thread(()-> {
task.run(TaskReentrantLock.condition1, TaskReentrantLock.condition2, "a");
}, "t1").start();
new Thread(()-> {
task.run(TaskReentrantLock.condition2, TaskReentrantLock.condition3, "b");
}, "t2").start();
new Thread(()-> {
task.run(TaskReentrantLock.condition3, TaskReentrantLock.condition1, "c");
}, "t3").start();
Sleeper.sleep(500); // 保证t1线程已进入条件变量
task.start();
}
}
class TaskReentrantLock{
private static ReentrantLock lock = new ReentrantLock();
public static Condition condition1 = lock.newCondition();
public static Condition condition2 = lock.newCondition();
public static Condition condition3 = lock.newCondition();
private int loopNum = 5;
public void start(){
lock.lock();
try{
condition1.signalAll();
}finally {
lock.unlock();
}
}
public void run(Condition condition, Condition nextCondition, String content){
for (int i = 0; i < loopNum; i++) {
lock.lock();
try {
try {
condition.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.print(content);
nextCondition.signal();
}finally {
lock.unlock();
}
}
}
}2.3 使用park/unaprk实现
t1、t2、t3线程开始都调用park阻塞住,等待其他线程唤醒它。最开始由主线程唤醒t1线程。
java
static Thread t1 = null,t2 = null,t3 = null;
public static void main(String[] args) {
t1 = new Thread(() -> {
run("a",t2);
}, "t1");
t2 = new Thread(() -> {
run("b",t3);
}, "t2");
t3 = new Thread(() -> {
run("c",t1);
}, "t3");
t1.start();
t2.start();
t3.start();
LockSupport.unpark(t1);
}
private static void run(String content, Thread nextThread){
for (int i = 0; i < 5; i++) {
LockSupport.park();
System.out.print(content);
LockSupport.unpark(nextThread);
}
}