一、前言

线程是 Java 进阶开发的分水岭,也是后端开发、高并发场景的核心基础。很多初学者学到多线程时容易陷入生命周期、锁、线程池参数的混乱。 本文从零梳理 Java 线程完整知识链路:从 3 种线程创建方式、线程安全同步锁、死锁问题,再到线程池七大核心参数,最后讲解CompletableFuture异步编程,配套可运行代码示例,看完就能上手开发并发程序。

二、三种创建线程方式

2.1 继承 Thread 类

继承Thread并重写run()方法,调用start()启动线程(不能直接调用 run,否则只是普通方法调用)。

java

运行

public class ThreadDemo extends Thread {
    @Override
    public void run() {
        System.out.println("线程执行:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        ThreadDemo t1 = new ThreadDemo();
        t1.setName("线程A");
        t1.start();
    }
}

优缺点: 优点:写法简单;缺点:Java 单继承,无法再继承其他类,扩展性差。

2.2 实现 Runnable 接口

实现Runnable接口,传入 Thread 构造器,规避单继承限制,推荐基础场景使用。

java

运行

public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable线程:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        RunnableDemo run = new RunnableDemo();
        new Thread(run, "线程B").start();
    }
}

2.3 Callable + Future 带返回值线程

前两种方式无返回值,Callable支持线程执行完返回结果,搭配FutureTask使用。

java

运行

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class CallableDemo implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 10; i++) sum += i;
        return sum;
    }

    public static void main(String[] args) throws Exception {
        CallableDemo call = new CallableDemo();
        FutureTask<Integer> task = new FutureTask<>(call);
        new Thread(task).start();
        // 获取线程返回结果
        System.out.println("计算结果:" + task.get());
    }
}

三、线程安全与同步锁 synchronized

3.1 线程安全问题演示

多线程共享同一变量时,会出现数据错乱(竞争资源):

java

运行

class Ticket implements Runnable{
    private int ticket = 10;
    @Override
    public void run() {
        while(ticket > 0){
            System.out.println("卖出票:" + ticket--);
        }
    }
}
// 多线程执行会出现重复票数、负数票

3.2 synchronized 同步锁解决方案

锁分为同步方法同步代码块,保证同一时间只有一个线程执行临界区代码。

  1. 同步代码块(推荐,锁粒度更小)

java

运行

@Override
public void run() {
    while(true){
        synchronized (this){
            if(ticket <= 0) break;
            System.out.println("卖出票:" + ticket--);
        }
    }
}
  1. 同步方法:锁对象为当前实例 this

java

运行

public synchronized void saleTicket(){
    if(ticket > 0){
        System.out.println("卖出票:" + ticket--);
    }
}

核心原理:内置监视器锁,独占式互斥,保证原子性、可见性、有序性。

四、死锁演示与避免

插入位置:4.1 代码示例下方 配图描述:双向循环依赖图,线程 1 持有 LockA 等待 LockB,线程 2 持有 LockB 等待 LockA,箭头形成闭环,标注死锁四大必要条件。

4.1 死锁代码示例

两个线程互相持有对方需要的锁,无限阻塞:

java

运行

public class DeadLockDemo {
    static Object lockA = new Object();
    static Object lockB = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lockA){
                System.out.println("线程1拿到锁A,等待锁B");
                try { Thread.sleep(1000); } catch (Exception e){}
                synchronized (lockB){
                    System.out.println("线程1执行完毕");
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (lockB){
                System.out.println("线程2拿到锁B,等待锁A");
                try { Thread.sleep(1000); } catch (Exception e){}
                synchronized (lockA){
                    System.out.println("线程2执行完毕");
                }
            }
        }).start();
    }
}

运行后程序永久卡住,无输出结束。

4.2 死锁四大必要条件

  1. 互斥条件:锁同一时间只能一个线程持有
  2. 请求保持:线程持有已有锁,再申请新锁
  3. 不可剥夺:锁只能主动释放,不能被抢占
  4. 循环等待:线程间形成环路等待

4.3 避免死锁方案

  • 统一锁获取顺序(所有线程先拿 lockA,再拿 lockB)
  • 设置锁超时时间
  • 减少嵌套同步代码块

五、线程池 7 大参数详解

频繁创建销毁线程开销极大,生产环境一律使用线程池ThreadPoolExecutor,核心 7 个构造参数:

java

运行

public ThreadPoolExecutor(
    int corePoolSize,          // 1.核心线程数
    int maximumPoolSize,       // 2.最大线程数
    long keepAliveTime,        // 3.非核心线程空闲存活时间
    TimeUnit unit,             // 4.时间单位
    BlockingQueue<Runnable> workQueue, // 5.阻塞队列
    ThreadFactory threadFactory,       // 6.线程工厂(自定义线程名)
    RejectedExecutionHandler handler   // 7.拒绝策略
)

参数逐条解析

  1. corePoolSize:常驻核心线程,无任务也不会销毁
  2. maximumPoolSize:核心 + 临时线程总数,临时线程 = max-core
  3. keepAliveTime:临时线程空闲多久后回收
  4. unit:时间单位(秒、毫秒、分钟)
  5. workQueue:等待队列,常用ArrayBlockingQueueLinkedBlockingQueue
  6. threadFactory:自定义线程命名,方便日志排查
  7. handler 拒绝策略(4 种内置)
    • AbortPolicy:直接抛出异常(默认)
    • CallerRunsPolicy:交给调用者线程执行
    • DiscardPolicy:丢弃当前任务
    • DiscardOldestPolicy:丢弃队列队首等待最久任务

线程池执行流程

  1. 任务进来,核心线程数未满 → 创建核心线程执行
  2. 核心线程已满 → 任务放入阻塞队列
  3. 队列满了 → 创建临时线程,直到达到 maxPoolSize
  4. 总线程达最大值 + 队列已满 → 触发拒绝策略

基础线程池代码示例

java

运行

import java.util.concurrent.*;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService pool = new ThreadPoolExecutor(
                2,
                5,
                2L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        // 提交10个任务测试
        for (int i = 1; i <= 10; i++) {
            int taskNum = i;
            pool.submit(() -> {
                System.out.println("执行任务:" + taskNum + " 线程:" + Thread.currentThread().getName());
            });
        }
        pool.shutdown();
    }
}

六、CompletableFuture 异步编程入门

JDK8 推出CompletableFuture,替代老旧Future,支持链式回调、多任务组合,简化异步开发。

6.1 无返回异步任务 runAsync

java

运行

// 线程池执行异步任务
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    System.out.println("异步任务执行:" + Thread.currentThread().getName());
}, pool);
future.get(); // 阻塞等待完成

6.2 有返回异步任务 supplyAsync

java

运行

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    return 100 * 2;
}, pool);
// 任务完成回调
future.thenAccept(res -> System.out.println("结果:" + res));

6.3 多任务组合常用 API

  • thenCombine:两个异步任务全部完成后合并结果
  • anyOf:任意一个任务完成就执行回调
  • allOf:等待所有任务全部执行完毕

java

运行

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 20);
// 合并两个结果求和
CompletableFuture<Integer> combine = f1.thenCombine(f2, (a, b) -> a + b);
System.out.println(combine.get()); // 输出30

七、总结

  1. 线程创建三种方式:ThreadRunnableCallable+Future,带返回值优先 Callable
  2. synchronized解决共享资源线程安全,嵌套锁易引发死锁,遵循统一锁顺序规避
  3. 生产禁止使用Executors快捷创建线程池,手动ThreadPoolExecutor自定义 7 大参数,控制资源
  4. JDK8 CompletableFuture简化异步编排,替代传统 Future,适合接口并行查询、多任务处理

更多推荐