你有没有试过一边煮咖啡,一边煎鸡蛋?在厨房里,这两件事可以同时进行,互不耽误。计算机世界里也有类似的操作方式——Java多线程就是让一个程序同时干好几件事的技术。
什么是线程?
每个Java程序启动时,默认会开启一个主线程。你可以把它想象成一个工人,按顺序执行任务。但如果任务多了,一个工人忙不过来,就得请更多人一起上。这些“工人”就是线程。
比如你在写一个文件上传功能,主线程负责界面响应,另一个线程在后台上传文件。这样即使上传卡住,界面也不会冻结。
创建线程的两种常用方式
第一种是继承 Thread 类,重写 run() 方法:
class MyThread extends Thread {
public void run() {
System.out.println("我在子线程工作");
}
}
// 启动线程
MyThread t = new MyThread();
t.start(); // 注意:不能直接调用 run()
第二种是实现 Runnable 接口,更推荐这种方式,因为Java不支持多继承:
class Task implements Runnable {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(1000); // 暂停1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 使用
Thread t = new Thread(new Task());
t.start();
线程安全问题:别让数据乱了套
多个线程同时修改同一个变量时,容易出问题。比如银行账户余额,两个线程同时取钱,可能都读到相同的余额,导致超支。
这时候需要用 synchronized 关键字来加锁:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
加上 synchronized 后,同一时间只有一个线程能进入方法,数据就不会冲突。
用线程池管理线程
频繁创建和销毁线程很耗资源。就像每次做饭都招新厨师,成本太高。更好的做法是建个“厨师团队”,重复使用。
Java 提供了 ExecutorService 来管理线程池:
ExecutorService pool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
pool.execute(new Task());
}
// 用完记得关闭
pool.shutdown();
这样最多同时运行3个线程,其余任务排队等待,系统更稳定。
实际场景:抢票系统模拟
春运抢票时,成千上万人同时点击。我们可以用多线程模拟这个场景:
public class TicketSeller {
private static int tickets = 10;
public static synchronized void sell() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " 卖出第 " + tickets-- + " 张票");
}
}
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
pool.execute(TicketSeller::sell);
}
pool.shutdown();
}
}
虽然有20个“用户”抢票,但只有10张票会被成功卖出,后面的会发现没票了。同步方法保证了不会出现负数票或重复卖票。
多线程不是万能药,用不好反而会让程序更难调试。关键是理解什么时候需要并发,什么时候该加锁,以及如何合理分配资源。