多线程篇-交替打印
背景
有如下需求:
线程1打印5次a,线程2打印5次b,线程3打印5次c,要求最后的结果是abcabcabcabc
步骤
简单情况
先考虑简单的情况,然后进行拓展。
简单版需求:
线程1打印1,线程2打印2,要求先打印2再打印1
我们先新建出线程1和线程2,代码如下:
新建线程
public static void main(String[] args) {
Thread thread1 = new Thread(() -> log.info("1"), "t1");
Thread thread2 = new Thread(() -> log.info("2"), "t2");
thread1.start();
thread2.start();
}
这里我们如果直接执行这个main方法的话,因为线程1先执行,所以更容易被cpu调度,所以结果大概率是1 2.但是,这个概率不是100%。我们的需求是先执行线程2,后执行线程1.考虑使用wait-notify实现。
wait-notify实现
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock){
while (!is2){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("1");
}
}, "t1");
Thread thread2 = new Thread(() -> {
synchronized (lock){
System.out.println("2");
is2 = true;
lock.notifyAll();
}
}, "t2");
thread1.start();
thread2.start();
}
第二步,使用布尔值is2来标识线程2是否已经执行结束,如果结束了,则唤醒线程1.
这样就保证了先打印2再打印1.
这里有两个点需要注意:
1.为什么使用while,而不是if,这个是为了防止虚假唤醒。什么意思呢,notify-wait的机制导致notify的时候你不知道你唤醒的是哪个线程,也就是说线程1可能被其他的线程3唤醒,加个while,如果唤醒之后不满足条件,则继续等待。
2.为什么使用notifyall,原因与1一致。
复杂情况
搞清楚了两个怎么做,考虑怎么放到多个上。两个的时候关键是有布尔值做标记,如果多个的话就没办法通过布尔值标记每个的状态了,所以考虑使用int标记每个的状态。
复杂情况解法
public class WaitTest {
static final Object lock = new Object();
static boolean is2 = false;//2是否执行了
static int flag = 1;
static int runNumber = 5;
//使用wait notify
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < runNumber; i++) {
synchronized (lock){
while (flag != 1){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("a");
flag = 2;
lock.notifyAll();
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < runNumber; i++) {
synchronized (lock){
while (flag != 2){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("b");
flag = 3;
lock.notifyAll();
}
}
});
Thread thread3 = new Thread(() -> {
for (int i = 0; i < runNumber; i++) {
synchronized (lock){
while (flag != 3){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("c");
flag = 1;
lock.notifyAll();
}
}
});
thread1.start();
thread2.start();
thread3.start();
}
}
上面的方法虽然可以打印出正确的结果,但是重复代码太多了,不够优雅,java是面向对象编程嘛,所以我们就想办法进行优化。
复杂情况解法优化
public class WaitTest {
// static int flag = 1;
static int runNumber = 5;
static final Object lock = new Object();
//使用wait notify
public static void main(String[] args) {
WaitNotify waitNotify = new WaitNotify(1);
Thread thread1 = new Thread(() -> {
waitNotify.print(runNumber,1, 2, "a");
});
Thread thread2 = new Thread(() -> {
waitNotify.print(runNumber, 2, 3, "b");
});
Thread thread3 = new Thread(() -> {
waitNotify.print(runNumber, 3, 1, "c");
});
thread1.start();
thread2.start();
thread3.start();
}
}
class WaitNotify{
static Object lock = new Object();
private int flag;
void print(int runNumber, int waitFlag, int nextFlag, String str){
for (int i = 0; i < runNumber; i++) {
synchronized (lock){
while (flag != waitFlag){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(str);
flag = nextFlag;
lock.notifyAll();
}
}
}
public WaitNotify(int flag) {
this.flag = flag;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!