一、线程基础:餐厅服务模型
场景类比:
一家网红餐厅(Web服务器)有3位服务员(线程),同时为多个食客(客户端请求)服务
每个顾客的订单处理流程独立进行
核心属性表:
模型要素 | 程序映射 | 实际开发意义 |
---|---|---|
服务窗口 | Thread对象 | 处理HTTP请求的独立单元 |
菜单任务 | Runnable实现类 | Servlet的service()逻辑 |
排队系统 | 线程池(ThreadPool) | Tomcat请求队列 |
二、线程创建的两种方式
1. Thread类继承(不推荐)
class OrderThread extends Thread {@Overridepublic void run() {// 处理订单逻辑(如数据库操作)}
}// Tomcat启动端口监听
new OrderThread().start();
缺点:
- 单继承限制
- 每请求新建线程资源消耗大
2. Runnable接口实现(推荐)
class PaymentTask implements Runnable {@Overridepublic void run() {// 支付处理逻辑(第三方API调用)}
}// 提交到线程池执行
ExecutorService pool = Executors.newFixedThreadPool(5);
pool.submit(new PaymentTask());
优势:
- 资源复用(线程池)
- 适合Servlet多请求处理:
// Servlet处理请求的伪代码
public class OrderServlet extends HttpServlet {public void doPost(HttpServletRequest req, HttpServletResponse resp) {Executor.submit(() -> processOrder(req)); // 异步处理}
}
程序检测示例:
if (thread.getState() == Thread.State.TIMED_WAITING) {logger.info("订单处理超时,启动补偿流程");
}
四、线程中断的两种实践
1. 手动标志位控制
class AuditThread implements Runnable {private volatile boolean stopped = false; // 需volatile保证可见性void stopProcess() { stopped = true; }@Overridepublic void run() {while (!stopped) {// 审计日志分析(可能耗时)}}
}
应用场景:
- 后台定时任务(如每日对账)
- 需优雅关闭的耗时操作
2. Interrupt机制示例
class MessageSender implements Runnable {@Overridepublic void run() {try {while (!Thread.currentThread().isInterrupted()) {sendMessage(); // 包含阻塞操作}} catch (InterruptedException e) {logger.warn("消息发送被终止");}}
}// 管理端触发停止
messageThread.interrupt();
中断触发情况:
中断方式 | 描述 |
---|---|
sleep()/wait() | 抛出InterruptedException |
正常执行中 | isInterrupted()标志置为true |
五、线程协作(join/synchronized)
1. 并发订单处理问题
public class OrderService {private int stock = 100; // 共享资源// 问题代码:未同步导致超卖public void deductStock() {if(stock > 0) stock--; }
}
实际运行结果:
可能多个线程同时判断stock>0导致负数
2. 同步解决方案对比
同步方式 | 实现示例 | 适用场景 |
---|---|---|
synchronized | 方法级锁 | 简单共享资源保护 |
Lock对象 | ReentrantLock | 需要灵活控制锁的释放 |
Atomic变量 | AtomicInteger安全计数 | 单变量高并发 |
最佳实践:
// 使用synchronized保证原子性
public synchronized void safeDeduct() {if(stock > 0) stock--;
}// 使用Atomic实现无需锁
private AtomicInteger atomicStock = new AtomicInteger(100);
public void atomicDeduct() {atomicStock.decrementAndGet();
}
六、Servlet中的多线程注意问题
典型场景:
public class DangerServlet extends HttpServlet {// 危险!存在线程安全问题的成员变量private int counter = 0; protected void doGet(...) {counter++; // 竞争条件}
}
解决方案:
- 同步控制:
synchronized (this) { counter++; }
- 改用局部变量
// 每个请求独立变量 int requestCounter = 0;
- 原子工具类:
private AtomicInteger safeCounter = new AtomicInteger(0); safeCounter.incrementAndGet();
七、知识应用案例
火车站有 4个售票窗口,每个窗口都是一个独立线程,共同售 500张北京到上海的动车票,要求实现:
①不超卖(同一张票不能售多次)
②不卖空(没有0号票和负数票)
初始问题代码:
public class TicketSystem {private int tickets = 500;public void sell() {if(tickets > 0) { // 模拟实际业务耗时try { Thread.sleep(20); } catch (Exception e) {}System.out.println(Thread.currentThread().getName() + " 售出: " + tickets--);} }public static void main(String[] args) {TicketSystem station = new TicketSystem();// 4个售票窗口竞争卖票for(int i=0; i<4; i++){new Thread(station::sell, "窗口"+(i+1)).start();}}
}
运行结果分析(问题展现)
错误情况1:重复售票
窗口1 售出: 500
窗口3 售出: 500
窗口4 售出: 499
窗口2 售出: 498
...
原因:多个线程同时进入tickets > 0
判断,导致多个窗口卖出同一号码的票
错误情况2:卖出0号票
窗口3 售出: 1
窗口1 售出: 0 // ⚠️非法票号
窗口4 售出: -1 // ⚠️灾难
原因:tickets--
操作分为读取、计算、写入三步,中间被其他线程打断
底层锁竞争机制图解
问题代码修正(同步优化)
public class TicketSystemFix {private int tickets = 500;private Object lock = new Object(); // 专用锁对象public void sell() {synchronized(lock) { // 通过锁包裹临界区if(tickets > 0) { try { Thread.sleep(20); } catch (Exception e) {}System.out.println(Thread.currentThread().getName() + " 售出: " + tickets--);}}}public static void main(String[] args) {TicketSystemFix station = new TicketSystemFix();for(int i=0; i<4; i++) {new Thread(station::sell, "[安全窗口" + (i+1) + "]").start();}}
}
修正后执行流程
正确的线程协作:
[安全窗口1] 售出: 500
[安全窗口3] 售出: 499
[安全窗口2] 售出: 498
[安全窗口4] 售出: 497
...
[安全窗口4] 售出: 1 // 最后一张,停止售票
关键现象:
- 输出票号严格递减无重复
- 当窗口1持有锁,其余窗口的售票机显示"请稍候…"(即BLOCKED状态)
阻塞细节与性能平衡
现实场景中锁的权衡
假设每个卖票操作需要:业务处理20ms(CPU占用)+ 数据库提交100ms(IO等待)
同步代码优化调整:
public void sellOptimized() {// 只同步数据修改部分,缩减锁范围int current; synchronized(lock) {if(tickets <= 0) return;current = tickets--; // 原子操作缩减到一行}// 异步处理耗时IO(不持有锁)processPayment(current); // 支付验证(耗时100ms)printTicket(current); // 出票(耗时20ms)
}
效果对比:
指标 | 全同步 | 部分同步 |
---|---|---|
吞吐量 | 10票/秒 | 250票/秒 |
CPU利用率 | 30%(受限锁争用) | 85%(高效) |
知识点总结
- 锁的本质:内存中的一个标记位,控制代码段的独占访问权
- 阻塞代价:被阻塞的线程会进入内核等待队列,上下文切换开销≈1-2微秒
- 使用原则:
- ✅同步核心数据变更
- ❌避免在锁内执行跨网络调用等长时操作
在JavaEE开发中,这种同步机制广泛用于:
- Servlet共享Session管理
- 数据库连接池分配
- 缓存雪崩保护(限流)
理解这些基础将帮助您在Spring框架中更好地使用@Transactional
等线程敏感注解。