一、线程与多线程基础

1. 什么是线程?

线程(Thread)是程序内部的一条执行流程。Java 程序的main方法是主线程的入口,程序中若只有一条执行流程,则称为单线程程序

public static void main(String[] args) {

    // 单线程执行:从main方法开始依次执行

    for (int i = 0; i < 10; i++) {

        System.out.println(i);

    }

}

2. 什么是多线程?

多线程是从软硬件层面实现多条执行流程的技术,多条线程由 CPU 调度执行。它能让程序同时处理多个任务,提高资源利用率。

3. 多线程的应用场景

  • 消息通信系统、电商平台(如淘宝、京东)、车票预订系统等需要同时处理多个用户请求的场景。
  • 计算密集型任务(如大数据量运算)可通过多线程拆分任务提升效率。

二、线程的创建方式

Java 提供三种核心线程创建方式,各有适用场景,需根据需求选择。

方式一:继承 Thread 类

实现步骤
  1. 定义子类继承java.lang.Thread
  2. 重写run()方法(定义线程执行逻辑);
  3. 创建子类对象;
  4. 调用start()方法启动线程(不可直接调用 run ())。
代码示例

// 1.定义线程类

class MyThread extends Thread {

    @Override

    public void run() {

        System.out.println("子线程执行...");

    }

}

// 2.使用线程

public class Demo {

    public static void main(String[] args) {

        MyThread thread = new MyThread();

        thread.start(); // 启动线程

        System.out.println("主线程执行...");

    }

}

优缺点

优点

缺点

编码简单,可直接使用 Thread 类方法

线程类继承 Thread 后无法继承其他类,存在单继承局限性

方式二:实现 Runnable 接口

实现步骤
  1. 定义任务类实现Runnable接口;
  2. 重写run()方法;
  3. 创建任务对象,交给Thread处理;
  4. 调用Threadstart()方法启动线程。
代码示例

// 1.定义任务类

class MyRunnable implements Runnable {

    @Override

    public void run() {

        System.out.println("子线程执行...");

    }

}

// 2.使用线程

public class Demo {

    public static void main(String[] args) {

        // 匿名内部类写法

        Thread thread1 = new Thread(new Runnable() {

            @Override

            public void run() {

                System.out.println("匿名内部类线程执行...");

            }

        });

        thread1.start();

        // Lambda表达式写法(JDK 8+)

        Thread thread2 = new Thread(() -> System.out.println("Lambda线程执行..."));

        thread2.start();

    }

}

优缺点

优点

缺点

任务类可继承其他类或实现其他接口,扩展性强

无法直接返回线程执行结果

方式三:实现 Callable 接口(JDK 5+)

前两种方式无法返回执行结果,Callable接口解决了这一问题。

实现步骤
  1. 定义任务类实现Callable<T>接口(T为返回值类型);
  2. 重写call()方法(有返回值);
  3. Callable对象封装为FutureTask<T>
  4. 交给Thread并调用start()启动;
  5. 通过FutureTask.get()获取结果。
代码示例

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.FutureTask;

// 1.定义任务类

class MyCallable implements Callable<Integer> {

    @Override

    public Integer call() throws Exception {

        System.out.println("子线程计算中...");

        return 100; // 返回计算结果

    }

}

// 2.使用线程

public class Demo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());

        Thread thread = new Thread(futureTask);

        thread.start();

        

        Integer result = futureTask.get(); // 阻塞等待结果

        System.out.println("子线程结果:" + result);

    }

}

优缺点

优点

缺点

扩展性强,支持返回执行结果

编码较复杂,get()方法会阻塞

三种方式对比

方式

核心特点

适用场景

继承 Thread

编码简单,单继承局限

简单场景,无需返回结果

实现 Runnable

扩展性强,无返回值

多线程共享资源,无需结果

实现 Callable

支持返回结果,扩展性强

需获取线程执行结果的场景

三、Thread 类常用方法

Thread类提供线程操作的核心 API,掌握这些方法是多线程编程的基础。

1. 构造器

构造器

说明

Thread(String name)

为线程指定名称

Thread(Runnable target)

封装 Runnable 任务为线程

Thread(Runnable target, String name)

封装任务并指定线程名称

2. 常用方法

方法

说明

void run()

线程任务方法,定义执行逻辑

void start()

启动线程(底层调用 run ())

String getName()

获取线程名称(默认格式:Thread - 索引)

void setName(String name)

设置线程名称

static Thread currentThread()

获取当前执行的线程对象

static void sleep(long time)

让当前线程休眠指定毫秒数(阻塞)

void join()

让调用该方法的线程先执行完毕

代码示例

public class ThreadMethodDemo {

    public static void main(String[] args) throws InterruptedException {

        // 获取并修改主线程名称

        Thread mainThread = Thread.currentThread();

        mainThread.setName("主线程");

        System.out.println("主线程名称:" + mainThread.getName());

        // 创建子线程

        Thread t1 = new Thread(() -> {

            for (int i = 0; i <= 3; i++) {

                System.out.println(Thread.currentThread().getName() + "执行:" + i);

                try {

                    Thread.sleep(100); // 休眠100毫秒

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

        }, "子线程A");

        t1.start();

        t1.join(); // 等待子线程A执行完毕

        System.out.println("主线程继续执行");

    }

}

四、线程安全问题

1. 什么是线程安全问题?

多个线程同时操作同一个共享资源且存在修改操作时,可能导致业务逻辑异常。

经典场景:夫妻共同账户取钱
  • 共享资源:余额 10 万元的银行账户;
  • 线程操作:小明和小红同时取 10 万元;
  • 问题风险:两人都判断余额充足,最终账户余额可能变为 - 10 万元。

2. 线程安全问题的成因

  • 多个线程同时执行;
  • 线程同时访问同一个共享资源;
  • 存在对共享资源的修改操作。

3. 模拟线程安全问题代码

import java.math.BigDecimal;

// 共享资源:银行账户

class Account {

    private String cardNo;

    private BigDecimal money; // 余额

    public Account(String cardNo, BigDecimal money) {

        this.cardNo = cardNo;

        this.money = money;

    }

    // getter和setter省略

}

// 取款操作类

class ATM {

    public void drawMoney(Account account, BigDecimal money) {

        // 判断余额是否充足

        if (account.getMoney().compareTo(money) < 0) {

            System.out.println(Thread.currentThread().getName() + ":余额不足");

            return;

        }

        // 模拟网络延迟

        try {

            Thread.sleep(20);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        // 扣减余额

        account.setMoney(account.getMoney().subtract(money));

        System.out.println(Thread.currentThread().getName() + "取款成功,余额:" + account.getMoney());

    }

}

// 线程类

class DrawThread extends Thread {

    private ATM atm;

    private Account account;

    private BigDecimal money;

    public DrawThread(ATM atm, Account account, BigDecimal money, String name) {

        super(name);

        this.atm = atm;

        this.account = account;

        this.money = money;

    }

    @Override

    public void run() {

        atm.drawMoney(account, money);

    }

}

// 测试类

public class ThreadSafeDemo {

    public static void main(String[] args) {

        Account account = new Account("ICBC001", new BigDecimal(100000));

        ATM atm = new ATM();

        new DrawThread(atm, account, new BigDecimal(100000), "小明").start();

        new DrawThread(atm, account, new BigDecimal(100000), "小红").start();

    }

}

可能结果:两人都取款成功,余额变为 - 100000(线程安全问题)。

五、线程同步:解决线程安全问题

线程同步的核心思想是让多个线程依次访问共享资源,通过 "加锁" 实现。

方式一:同步代码块

语法

synchronized(同步锁对象) {

    // 访问共享资源的核心代码

}

代码示例(修复取款问题)

class ATM {

    public void drawMoney(Account account, BigDecimal money) {

        // 同步代码块:使用共享资源account作为锁

        synchronized (account) {

            if (account.getMoney().compareTo(money) < 0) {

                System.out.println(Thread.currentThread().getName() + ":余额不足");

                return;

            }

            try {

                Thread.sleep(20);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            account.setMoney(account.getMoney().subtract(money));

            System.out.println(Thread.currentThread().getName() + "取款成功,余额:" + account.getMoney());

        }

    }

}

锁对象规范
  • 必须是同一对象(对所有并发线程唯一);
  • 实例方法推荐用this
  • 静态方法推荐用类名.class(字节码对象)。

方式二:同步方法

语法

修饰符 synchronized 返回值类型 方法名(参数列表) {

    // 操作共享资源的代码

}

代码示例

class ATM {

    // 同步方法:锁对象为this(ATM实例)

    public synchronized void drawMoney(Account account, BigDecimal money) {

        // 取款逻辑同上

    }

}

锁对象原理
  • 实例方法:默认锁对象为this
  • 静态方法:默认锁对象为类名.class
与同步代码块对比

维度

同步代码块

同步方法

锁范围

仅核心代码(小)

整个方法(大)

灵活性

高(可精确控制)

可读性

稍差

方式三:Lock 锁(JDK 5+)

Lock是接口,常用实现类ReentrantLock,比synchronized更灵活。

核心 API

方法

说明

ReentrantLock()

创建 Lock 锁对象

void lock()

获取锁

void unlock()

释放锁

代码示例

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

class ATM {

    // 定义锁对象(推荐用final修饰)

    private final Lock lock = new ReentrantLock();

    public void drawMoney(Account account, BigDecimal money) {

        lock.lock(); // 加锁

        try {

            // 取款逻辑同上

        } finally {

            lock.unlock(); // 释放锁(必须放finally中)

        }

    }

}

Lock 锁的高级功能
  • 公平锁new ReentrantLock(true),按线程请求顺序获取锁;
  • 锁中断lock.lockInterruptibly(),可中断等待锁的线程;
  • 尝试锁lock.tryLock(10, TimeUnit.SECONDS),超时未获取则返回 false。
三种同步方式对比

方式

特点

适用场景

同步代码块

灵活控制锁范围

局部核心代码需同步

同步方法

简单直观,锁范围大

整个方法需同步

Lock 锁

功能强大(公平锁、中断等)

复杂同步场景

六、线程池:高效管理线程资源

1. 为什么需要线程池?

  • 频繁创建线程会消耗大量资源(CPU、内存);
  • 线程池可复用线程,减少创建 / 销毁开销,控制线程数量。

2. 线程池核心概念

  • ExecutorService:线程池接口;
  • 核心线程:长期保留的线程(正式工);
  • 临时线程:任务繁忙时创建,空闲后销毁(临时工);
  • 任务队列:存放等待执行的任务;
  • 拒绝策略:任务队列满且线程都繁忙时的处理方式。

3. 创建线程池的方式

方式一:ThreadPoolExecutor(推荐)

通过ThreadPoolExecutor类直接创建,可精确控制参数。

核心构造器

public ThreadPoolExecutor(

    int corePoolSize, // 核心线程数(正式工数量)

    int maximumPoolSize, // 最大线程数(正式工+临时工)

    long keepAliveTime, // 临时线程存活时间

    TimeUnit unit, // 时间单位(秒、分等)

    BlockingQueue<Runnable> workQueue, // 任务队列

    ThreadFactory threadFactory, // 线程工厂

    RejectedExecutionHandler handler // 拒绝策略

)

代码示例

import java.util.concurrent.*;

public class ThreadPoolDemo {

    public static void main(String[] args) {

        // 创建线程池

        ExecutorService pool = new ThreadPoolExecutor(

            3, // 核心线程数3

            5, // 最大线程数5(临时工2人)

            5, // 临时线程存活5秒

            TimeUnit.SECONDS,

            new ArrayBlockingQueue<>(3), // 任务队列容量3

            Executors.defaultThreadFactory(),

            new ThreadPoolExecutor.AbortPolicy() // 拒绝策略

        );

        // 提交任务

        for (int i = 1; i <= 9; i++) {

            int taskNo = i;

            pool.execute(() -> {

                System.out.println(Thread.currentThread().getName() + "执行任务" + taskNo);

                try {

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            });

        }

        pool.shutdown(); // 任务执行完毕后关闭线程池

    }

}

线程池执行流程
  1. 新任务提交时,先判断核心线程是否空闲,空闲则执行;
  2. 核心线程繁忙,任务加入队列等待;
  3. 队列满且未达最大线程数,创建临时线程执行;
  4. 队列满且达最大线程数,执行拒绝策略。
方式二:Executors 工具类(不推荐大型系统)

Executors提供静态方法快速创建线程池,但存在资源耗尽风险。

方法

说明

风险

newFixedThreadPool(n)

固定线程数的线程池

队列容量过大(Integer.MAX_VALUE),可能 OOM

newSingleThreadExecutor()

单线程线程池

同上

newCachedThreadPool()

动态增减线程

线程数过大(Integer.MAX_VALUE),可能 OOM

newScheduledThreadPool(n)

延迟 / 定期执行任务

同上

注意:阿里巴巴开发手册强制要求避免使用Executors,推荐ThreadPoolExecutor手动创建。

4. 线程池常用方法

方法

说明

void execute(Runnable task)

执行 Runnable 任务(无返回值)

Future<T> submit(Callable<T> task)

执行 Callable 任务,返回 Future 对象

void shutdown()

等待所有任务完成后关闭线程池

List<Runnable> shutdownNow()

立即关闭,返回未执行的任务

5. 任务拒绝策略

策略

说明

AbortPolicy

丢弃任务并抛出异常(默认)

DiscardPolicy

丢弃任务,不抛异常(不推荐)

DiscardOldestPolicy

丢弃队列中最老任务,加入新任务

CallerRunsPolicy

由提交任务的线程执行被拒绝的任务

6. 核心线程数配置建议(面试重点)

  • IO 密集型任务(如文件读写、网络请求):核心线程数 ≈ CPU 核心数 × 2~3;
  • 计算密集型任务(如大数据运算):核心线程数 ≈ CPU 核心数 + 1;

解释:IO 密集型任务 CPU 空闲时间多,可多创建线程;计算密集型任务 CPU 繁忙,线程数过多会增加切换开销。

七、并发与并行

多线程的执行方式分为并发和并行,两者通常同时存在。

1. 并发(Concurrency)

  • 含义:CPU 通过轮询调度为多个线程服务(同一时刻仅一个线程执行),因切换速度快,给人 "同时执行" 的感觉;
  • 适用场景:单核 CPU 处理多线程。

2. 并行(Parallelism)

  • 含义:同一时刻有多个线程被 CPU 执行(多核 CPU 的多个核心同时处理);
  • 适用场景:多核 CPU 处理多线程。

总结

多线程执行是并发与并行的结合:CPU 在多核间分配线程实现并行,单个核心通过轮询实现并发。

结语

多线程是 Java 开发的核心知识点,掌握线程创建、同步机制、线程池优化等内容,能显著提升程序性能和稳定性。实际开发中需根据场景选择合适的线程创建方式和同步策略,尤其注意线程安全和资源管理。建议多通过实战案例练习,加深对多线程原理的理解。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐