Java 多线程编程

进程是操作系统分配资源的基本单位,线程是进程内的执行单元。同一进程的多个线程共享内存,能"同时"完成多件事——边下载边聊天、一台服务器同时服务上万用户都靠多线程;但并发访问共享数据会引发线程安全问题。本节讲清创建线程的三种方式与生命周期,并用经典"卖票"示例吃透 synchronized。

线程与进程

  • 进程:独立运行的程序实例,拥有独立内存,如一个浏览器;线程:进程内的执行流,共享进程内存,创建与切换开销更小。
  • Java 天生多线程:main 是主线程,垃圾回收等工作在后台线程运行,JVM 本身就是多线程环境。

创建线程的三种方式

继承 Thread、实现 Runnable、实现 Callable(可返回结果)都行,都要 start() 启动(直接调 run() 不会新建线程);片段需放进 main 方法中运行:

// 方式一:继承 Thread,重写 run()
Thread t1 = new Thread() {
    public void run() { System.out.println("方式一:继承 Thread"); }
};
t1.start();
// 方式二:实现 Runnable,配 Lambda(推荐)
new Thread(() -> System.out.println("方式二:Runnable + Lambda")).start();
// 方式三:Callable 可返回结果
FutureTask<Integer> ft = new FutureTask<>(() -> 1 + 2);
new Thread(ft).start();
System.out.println(ft.get());   // 输出:3(需处理异常)

线程生命周期

状态依次为:新建(new)→就绪(runnable)→运行(running)→阻塞(blocked)→死亡(terminated),常用 sleep/join 控制:

// 片段需放进 main 方法中运行
Thread t = new Thread(() -> {
    for (int i = 1; i <= 3; i++) {
        try { Thread.sleep(100); } catch (InterruptedException e) {}  // 休眠 100 毫秒
        System.out.println("子线程 i=" + i);
    }
});
t.start();
t.join();            // 主线程等待子线程执行完
System.out.println("主线程结束");

sleep 让当前线程让出 CPU 指定毫秒;join 让调用方等待目标线程执行完毕。

线程安全问题:卖票

三个窗口同时卖 10 张票,"判断还有票"与"票数自减"两步之间可能被其他线程插队,造成卖重或卖超

// 片段需放进 main 方法中运行
class Ticket implements Runnable {
    private int tickets = 10;
    public void run() {
        while (tickets > 0) {
            System.out.println(Thread.currentThread().getName()
                    + " 卖出第" + tickets-- + "张票");
        }
    }
}
Ticket task = new Ticket();
new Thread(task, "窗口A").start();
new Thread(task, "窗口B").start();
new Thread(task, "窗口C").start();

不加锁时输出会出现两张"第 5 张"甚至负数——多线程访问共享变量必须同步。

synchronized 同步方法与同步块

方法上加 synchronized 即同步方法(锁 this);同步块 synchronized (this) { ... } 只锁临界区,锁范围越小并发越高:

// 片段需放进 main 方法中运行
class Ticket implements Runnable {
    private int tickets = 10;
    public void run() {
        while (tickets > 0) sell();    // 循环内取票,锁粒度更小
    }
    public synchronized void sell() {   // 同一时刻仅一个线程能进入
        if (tickets > 0) {
            System.out.println(Thread.currentThread().getName()
                    + " 卖出第" + tickets-- + "张票");
        }
    }
}

运行节选:窗口A 卖出第10张票、窗口C 卖出第9张票、窗口B 卖出第8张票……顺序不定,票号 10→1 不重不漏。

死锁简介

两个线程各持一把锁、又互相等待对方释放,将永远阻塞(死锁):

// 片段需放进 main 方法中运行
Object a = new Object(), b = new Object();
Runnable r1 = () -> { synchronized (a) { try { Thread.sleep(50); } catch (InterruptedException e) {} synchronized (b) { } } };
new Thread(r1).start();
Runnable r2 = () -> { synchronized (b) { try { Thread.sleep(50); } catch (InterruptedException e) {} synchronized (a) { } } };
new Thread(r2).start();

预防原则:所有线程按相同顺序获取多把锁,尽量减少锁嵌套与持锁时间。

小结:多线程开发记住三件事——用 Runnable/Callable 创建线程、用 synchronized 锁住共享数据的写操作、多把锁时统一加锁顺序防死锁。

笔记加载中…