当前位置: 首页 > news >正文

【算法】多线程执行顺序控制(方法详解易懂版)5.27

问题要求:

有三个线程 A、B、C,分别调用 first()second()third(),但必须保证:

  1. first() 先执行,
  2. 然后 second()
  3. 最后 third()
  4. 但由于线程是并发执行的,操作系统可能会随机调度它们的顺序,所以需要强制让它们按顺序执行

方法 1:

volatile 变量(最简单)

思路:
  • 设置一个变量 flag,用来标记当前应该执行哪个方法。
  • first() 执行完后,修改 flag,告诉 second() 可以运行了。
  • second() 执行完后,修改 flag,告诉 third() 可以运行了。
代码:
public class Foo {    
private volatile int flag = 1;// 1表示first可以执行,2表示second,3表示third    public void first() {        System.out.print("first");        flag = 2; // 告诉second可以执行了    }    public void second() throws InterruptedException{        while (flag != 2) {            // 如果flag不是2,就循环等待       }        System.out.print("second");        flag = 3; // 告诉third可以执行了   }    public void third() throws InterruptedException {        while (flag != 3) {           // 如果flag不是3,就循环等待       }       System.out.print("third");    }}
解释:
  • volatile 保证 flag 的修改对所有线程可见(避免缓存问题)。
  • second()third() 会不断检查 flag,直到 flag 变成它们需要的值才执行。
缺点:
  • while 循环会占用 CPU(忙等待),不太高效。

方法 2:

synchronized + wait/notify(更高效)

思路:
  • synchronized 加锁,保证同一时间只有一个线程能进入关键代码。
  • wait() 让线程等待,notifyAll() 唤醒其他线程。
代码:
public class Foo {    
private boolean firstDone = false;    
private boolean secondDone = false;    
private final Object lock = new Object(); // 锁对象    public void first() {        
synchronized (lock) {            
System.out.print("first");           firstDone = true;            lock.notifyAll(); // 唤醒所有等待的线程        }    }    public void second() throws InterruptedException {        
synchronized (lock) {            while (!firstDone) {                lock.wait(); // 如果first没执行完,就等待            }            System.out.print("second");            secondDone = true;            lock.notifyAll(); // 唤醒third        }    }    public void third() throws InterruptedException {        synchronized (lock) {            while (!secondDone) {                lock.wait(); // 如果second没执行完,就等待            }           System.out.print("third");        }    }}
解释:
  • synchronized 保证同一时间只有一个线程能进入 lock 保护的代码块。
  • wait() 会让线程释放锁并等待,直到被 notifyAll() 唤醒。
  • first() 执行完后,notifyAll() 会唤醒 second()second() 执行完后唤醒 third()
优点:
  • volatile 更高效,因为 wait() 不会占用 CPU。

方法 3:

CountDownLatch(推荐)

思路:
  • CountDownLatch 是一个计数器,初始值为 1。
  • await() 会阻塞线程,直到计数器变成 0。- countDown() 会让计数器减 1。
代码:
import java.util.concurrent.CountDownLatch;
public class Foo {    
private CountDownLatch latch1 = new CountDownLatch(1); 
// first -> second    
private CountDownLatch latch2 = new CountDownLatch(1); 
// second -> third    
public void first() {        
System.out.print("first");        
latch1.countDown(); // 让second可以执行    
}    
public void second() throws InterruptedException {        
latch1.await(); // 等待first执行完        
System.out.print("second");        
latch2.countDown(); // 让third可以执行    
}    
public void third() throws InterruptedException {        
latch2.await(); // 等待second执行完        
System.out.print("third");    }
}
解释:
  • latch1 控制 first() -> second()
  • first() 执行完后,latch1.countDown()latch1 变成 0,second()await() 就会放行。
  • latch2 控制 second() -> third()
  • second() 执行完后,latch2.countDown()latch2 变成 0,third()await() 就会放行。
优点:
  • 代码简洁,适合这种固定顺序的线程控制。

方法 4:

Semaphore(信号量)

思路:
  • Semaphore 可以控制同时访问的线程数量。
  • 初始值为 0 的信号量,acquire() 会阻塞,直到 release() 被调用。
代码:
import java.util.concurrent.Semaphore;
public class Foo {    
private Semaphore sem1 = new Semaphore(0); 
// first -> second    
private Semaphore sem2 = new Semaphore(0); 
// second -> third    
public void first() {        
System.out.print("first");        
sem1.release(); // 让second可以执行    
}    
public void second() throws InterruptedException {        
sem1.acquire(); // 等待first执行完        
System.out.print("second");        
sem2.release(); // 让third可以执行    
}    
public void third() throws InterruptedException {        
sem2.acquire(); 
// 等待second执行完        
System.out.print("third");    }}
解释:
  • sem1 控制 first() -> second()
  • first() 执行完后 sem1.release()second()sem1.acquire() 就会放行。
  • sem2 控制 second() -> third(): - second() 执行完后 sem2.release()third()sem2.acquire() 就会放行。
优点:
  • 灵活,适合更复杂的线程控制。

推荐

  • 如果是面试或简单场景,用 CountDownLatchvolatile
  • 如果需要更灵活的控制,用 Semaphoresynchronized

相关文章:

  • 基于stm32的 永磁同步电机二电平驱动控制系统设计
  • Silvaco TCAD 2020 Windows版本安装教程
  • OpenKylin文件管理器界面层级切换问题
  • kernel版本号
  • 依赖倒置原则 (Dependency Inversion Principle, DIP)
  • 实时商品数据对接实战:唯品会 API 接口调用与详情页采集教程
  • 主键与唯一键详解:概念、区别与面试要点
  • uniapp-商城-72-shop(5-商品列表,购物车实现回顾)
  • 触觉智能RK3506星闪开发板规格书 型号IDO-EVB3506-V1
  • STM32之IIC(重点)和OLED屏
  • 开源模型应用落地-模型上下文协议(MCP)-安全认证的创新与实践探索(十)
  • Win键+R键快捷命令汇总
  • Linux 资源限制(进程级,用户级,系统级)
  • [特殊字符]《计算机组成原理》第 8 章 - CPU 的结构和功能
  • ROS2学习(15)------ROS2 TF2 机器人坐标系管理器
  • 使用硬件调试器认识arm64的四大特权级
  • WPF【11_1】WPF实战-重构与美化(Entity Framework)
  • 【网络编程】十七、多路转接之 epoll
  • 想查看或修改 MinIO 桶的匿名访问权限(public/private/custom)
  • gdiplus,GDI +为什么2001年发布后几乎没有再更新了
  • 济宁市任城区建设局网站/新闻发布系统
  • 克隆视厅网站怎么做/百度指数是免费的吗
  • 注册个人网站的方法/云和数据培训机构怎么样
  • 连云港网站建设案例/网站优化技术
  • 专门查大学的网站/百度游戏排行榜风云榜
  • 外国网站欣赏/seo整站优化推广