java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java 线程使用

Java 线程基本概念、使用方法最全详解

作者:猩火燎猿

本文详细介绍了Java线程的基本概念、使用方法、线程同步和安全、线程间通信、线程状态、线程池以及拒绝策略等,通过理论知识和实际示例,帮助读者全面理解和掌握Java线程编程,感兴趣的朋友跟随小编一起看看吧

一、什么是线程

线程是程序执行的最小单位,是进程中的一个执行流。一个进程可以包含多个线程,这些线程共享进程的资源,但有各自的执行路径。

二、Java线程的基本使用

1. 创建线程的方式

(1)继承 Thread 类

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程运行:" + Thread.currentThread().getName());
    }
}
MyThread t1 = new MyThread();
t1.start(); // 启动线程

(2)实现 Runnable 接口

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程运行:" + Thread.currentThread().getName());
    }
}
Thread t2 = new Thread(new MyRunnable());
t2.start();

(3)实现 Callable 接口(带返回值)

import java.util.concurrent.*;
class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() {
        System.out.println("线程运行:" + Thread.currentThread().getName());
        return 123;
    }
}
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(new MyCallable());
Integer result = future.get(); // 阻塞获取返回值
executor.shutdown();

2. 线程的生命周期状态

3. 线程常用方法

三、线程的同步和安全

由于多个线程共享内存,容易出现线程安全问题,常用的同步方式有:

四、线程间通信

常用方式:

五、线程状态

Java线程的六种状态(Thread.State 枚举)

状态转换示意图

NEW
 ↓
RUNNABLE ←→ BLOCKED
 ↓         ↕
WAITING ←→ TIMED_WAITING
 ↓
TERMINATED

示例代码

Thread t = new Thread(() -> {
    System.out.println("线程运行中");
});
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE

线程状态查询

通过 Thread.getState() 方法可以获得当前线程的状态,返回值是上面这些枚举类型。

使用 jstack 工具

jstack 是 JDK 自带的线程堆栈分析工具。它可以输出所有线程的堆栈信息,包括线程状态和锁的详细信息。

步骤:

找到你的 Java 进程 PID(比如用 jps 命令)。

执行:

jstack <PID>

在输出中查找 BLOCKED 状态的线程,会显示类似:

"Thread-1" #12 prio=5 os_prio=0 tid=0x000000001a2b3800 nid=0x1c34 BLOCKED
on object monitor owned by "Thread-2" tid=0x000000001a2b4000
   at com.example.MyClass.myMethod(MyClass.java:23)
   -  blocked on <0x000000076b1c1b20> (a java.lang.Object)
   -  locked <0x000000076b1c1b20> (a java.lang.Object)

blocked on <0x000000076b1c1b20> 表示线程正在等待这个对象的锁。

owned by "Thread-2" 表示这个锁当前被哪个线程持有。

2. 使用可视化工具

3. 代码层面辅助排查

虽然不能直接在 Java 代码中获取“当前 BLOCKED 等待哪个锁”,但可以通过合理设计日志、监控代码,辅助定位。例如:

4. 结合堆栈分析

通过堆栈可以看到线程卡在哪一行代码,通常是 synchronized 相关的代码块或方法。

六、什么是线程池

线程池(Thread Pool)是一种线程管理方式。它预先创建一批线程,任务来了就复用这些线程,而不是每次都新建/销毁线程,从而提高性能,减少系统开销。

七、线程池的核心优势

  1. 降低资源消耗:避免频繁创建/销毁线程。
  2. 提高响应速度:任务到来时可直接复用现有线程。
  3. 便于管理:可统一分配、调度线程,提高系统稳定性。

八、Java中的线程池实现

Java 提供了线程池相关的 API,主要在 java.util.concurrent 包下。

1. 主要类和接口

2. 常用线程池创建方式

ExecutorService pool = Executors.newFixedThreadPool(5); // 固定大小线程池
ExecutorService pool = Executors.newCachedThreadPool();  // 可变大小线程池
ExecutorService pool = Executors.newSingleThreadExecutor(); // 单线程池
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2); // 定时任务线程池

建议: 实际项目中推荐直接使用 ThreadPoolExecutor,而不是 Executors 工厂方法,因为可以更细致地配置参数,避免资源耗尽等风险。

九、ThreadPoolExecutor核心参数

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    corePoolSize,        // 核心线程数
    maximumPoolSize,     // 最大线程数
    keepAliveTime,       // 非核心线程存活时间
    unit,                // 时间单位
    workQueue,           // 任务队列
    threadFactory,       // 线程工厂
    handler              // 拒绝策略
);

十、线程池工作流程图

提交任务
   ↓
线程池检查是否有空闲线程
   ↓
有 → 直接执行
无 → 进入任务队列
   ↓
队列满 → 创建新线程(不超过最大线程数)
   ↓
线程池满/队列满 → 拒绝策略

十一、线程池常用方法

十二、简单示例

ExecutorService pool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
    pool.execute(() -> {
        System.out.println(Thread.currentThread().getName() + " 执行任务");
    });
}
pool.shutdown();

十二、线程池使用注意事项

  1. 合理配置参数:结合实际业务和机器资源设置线程池参数。
  2. 避免任务堆积:队列过长可能导致内存溢出。
  3. 自定义拒绝策略:根据业务需求处理无法执行的任务。
  4. 线程安全:任务本身要保证线程安全。

十三、总结

十四、什么是拒绝策略?

当线程池中的任务数量超过了线程池的最大承载能力(即:线程池最大线程数和任务队列都满了),此时再提交新任务,线程池就会触发拒绝策略,决定如何处理这些无法立即执行的任务。

十五、拒绝策略的触发条件

线程池执行任务的流程大致如下:

  1. 线程数 < corePoolSize:创建新线程执行任务。
  2. 线程数 ≥ corePoolSize,队列未满:任务入队列等待执行。
  3. 队列满,线程数 < maximumPoolSize:创建新线程执行任务。
  4. 线程数 = maximumPoolSize,队列也满:触发拒绝策略

十六、Java内置的4种拒绝策略

Java通过java.util.concurrent.ThreadPoolExecutor提供了4种常用的拒绝策略,分别实现了RejectedExecutionHandler接口:

1. AbortPolicy(默认策略)

直接抛出异常RejectedExecutionException),阻止系统正常运行。

public static class AbortPolicy implements RejectedExecutionHandler {
    public AbortPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}

适用场景:希望程序员能及时发现问题,适合对任务丢失不能容忍的场合。

2. CallerRunsPolicy

由调用线程(提交任务的线程)自己执行该任务,不会抛异常。

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public CallerRunsPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

适用场景:希望不丢任务,但会降低提交任务线程的速度,间接缓解线程池压力。

3. DiscardPolicy

直接丢弃任务,不做任何处理,也不抛异常。

public static class DiscardPolicy implements RejectedExecutionHandler {
    public DiscardPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        // 什么都不做,任务被丢弃
    }
}

适用场景:对丢失部分任务可以容忍,不希望抛异常影响主流程。

4. DiscardOldestPolicy

丢弃队列中最早的(最旧的)一个任务,然后尝试再次提交当前任务。

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public DiscardOldestPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll(); // 移除队列头部的一个任务
            e.execute(r);        // 再尝试提交当前任务
        }
    }
}

适用场景:优先保证新任务执行,适合对旧任务时效性要求不高的场合。

十七、自定义拒绝策略

你可以实现 RejectedExecutionHandler 接口,自定义处理逻辑,例如:

示例代码:

public class MyRejectHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println("任务被拒绝:" + r.toString());
        // 这里可以做日志、报警、持久化等操作
    }
}

使用方法:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2, 4, 60, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(2),
    new MyRejectHandler()
);

十八、实际应用建议

到此这篇关于Java 线程最全详解的文章就介绍到这了,更多相关Java 线程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文