线程是程序执行的最小单位,是进程内部比进程更小的执行单元。每个线程完成一个独立的任务,并与其他线程共享所属进程的资源。Java 中的线程可以通过继承 Thread 类或实现 Runnable 接口来创建和启动。线程代码必须编写在 run() 方法中,而该方法不能直接调用,必须通过调用 start() 方法来开启一个新的线程并自动执行 run() 方法中的内容。

程序中的主线程是由 main 方法启动的,是程序执行的入口点。在主线程中可以创建并启动其他子线程,这些子线程与主线程一起并发执行。由于线程之间是抢占式地使用 CPU 资源,因此线程的执行顺序并不一定与启动顺序一致。

Java 支持多线程编程,使得应用程序可以同时执行多个任务,从而提高 CPU 的利用率和程序的响应速度。线程池是 Java 中一种常见的多线程处理形式,通过将任务提交到线程池队列中,由线程池自动调度线程来执行这些任务,线程池中的线程通常都是后台线程(即守护线程)。

1. 创建线程

  1. Thread类
  2. Runnable接口
  3. Callable接口(JDK8新增,带返回值,性能较差)
Thread
public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 100; i++) {
            System.out.println(String.format("MyThread:   %s time invoke.", i));
        }
    }
}
Runnable
public class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(String.format("MyRunnable: %s time invoke.", i));
        }
    }
}
Callable
public class MyCallable implements Callable {

    @Override
    public Object call() throws Exception {
        int i = 0;
        for (; i < 100; i++) {
            System.out.println(String.format("MyCallable: %s time invoke.", i));
        }
        return i;
    }
}
调用线程
// 第一种,继承Thread类
Thread t1 = new MyThread();
t1.start();

// 第二种,实现Runnable接口
Thread t2 = new Thread(new MyRunnable());
t2.start();

// 第三种,实现Callable接口
FutureTask task = new FutureTask(new MyCallable());
Thread t3 = new Thread(task);
t3.start();

try {
    // 获取t3的返回值
    Object o = task.get();
    System.out.println(String.format("task invoked result: %s.", o));  // task invoked result: 100.
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

2. 线程状态

获取线程状态
thread.getState();
NEW(新建状态)

线程对象被创建,但尚未启动。此时线程还未调用 start() 方法,因此它并不处于可运行状态。

RUNNABLE(可运行状态)

线程已经启动并进入可运行状态。这表示线程正在JVM中运行,或者正在等待操作系统分配处理器资源。即使线程因为等待处理器而暂时没有运行,它仍然处于 RUNNABLE 状态。

BLOCKED(阻塞状态)

线程因等待获取监视器锁而处于阻塞状态。这种情况通常发生在多个线程尝试进入同步代码块或方法时。

WAITING(等待状态)

线程因调用某些方法(如 Object.wait()Thread.join())而无限期等待,直到其他线程采取特定操作。

TIMED_WAITING(限时等待状态)

线程因调用带有超时参数的方法(如 Thread.sleep(long millis)Object.wait(long timeout))而进入限时等待状态。

TERMINATED(终止状态)

线程任务执行完成或因异常而终止。

3. 修改线程名称

Thread t = new MyThread();
// 修改线程名称
System.out.println(t.getName());  // Thread-0
t.setName("线程-云深不知处");
System.out.println(t.getName());  // 线程-云深不知处

4. 定时任务

任务的调度和执行依赖于底层的线程机制,因此定时任务与线程之间是任务与执行者的关系。

Date date = new Date();
long time = date.getTime();

// 创建定时任务
Timer timer = new Timer();
TimerTask task = new TimerTask() {
    @Override
    public void run() {
        Date date1 = new Date();
        long time1 = date1.getTime();
        long second = (time1 - time) / 1000;

        if (second >= 10) {
            // 取消定时任务
            timer.cancel();
            return;
        }

        System.out.println(String.format("定时任务第 %s 次执行。", second));
    }
};

// 延迟0,每1秒执行一次
timer.schedule(task, 0, 1000);

5. 守护线程

Java中的垃圾回收机制(GC)是由JVM在后台自动执行的内存管理功能,其运行依赖于特定的线程模型。垃圾回收机制通常运行在守护线程(Daemon Thread)中。守护线程是一种为其他非守护线程提供服务的线程,当所有用户线程(非守护线程)结束时,守护线程会自动终止,JVM也会随之退出。

由于垃圾回收线程是守护线程,其优先级通常较低,这意味着它在系统资源充足时才会运行,例如在CPU空闲或内存压力较低时进行垃圾回收。这种设计保证了垃圾回收机制不会对应用程序的性能造成显著影响。

判断是否守护线程
thread.isDaemon();
设置为守护线程
thread.setDaemon(true);
thread.start();
查看当前JVM中的守护线程
// 查看当前JVM中的守护线程
public class Test_DaemonThreadList {
    public static void main(String[] args) {
        ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
        while (rootGroup.getParent() != null) {
            rootGroup = rootGroup.getParent();
        }

        Thread[] daemonThreads = new Thread[rootGroup.activeCount()];
        rootGroup.enumerate(daemonThreads);

        for (Thread thread : daemonThreads) {
            if (thread != null) {
                System.out.println("线程名称: " + thread.getName() +
                        ", 守护状态: " + thread.isDaemon() +
                        ", 状态: " + thread.getState());
            }
        }

        /**
         * 线程名称: Reference Handler, 守护状态: true, 状态: WAITING
         * 线程名称: Finalizer, 守护状态: true, 状态: WAITING
         * 线程名称: Signal Dispatcher, 守护状态: true, 状态: RUNNABLE
         * 线程名称: Attach Listener, 守护状态: true, 状态: RUNNABLE
         * 线程名称: main, 守护状态: false, 状态: RUNNABLE
         */
    }
}

6. 线程优先级

在多线程环境中,每个线程都有一个优先级,操作系统会根据优先级高低决定线程的执行顺序。
高优先级的线程一旦进入可执行状态,会抢占低优先级线程的CPU资源,即使低优先级线程正在运行,也会被暂停执行,让位于高优先级线程。
可以通过 setPriority(int p) 方法设置线程的优先级,取值范围为1到10,其中 Thread.MAX_PRIORITY 表示最高优先级,Thread.MIN_PRIORITY 表示最低优先级。

// 优先级
Thread t1 = new MyThread();
Thread t2 = new Thread(new MyRunnable());

System.out.println(String.format("MyThread priority: %s", t1.getPriority()));  // MyThread priority: 5
System.out.println(String.format("MyRunnable priority: %s", t2.getPriority()));  // MyRunnable priority: 5
t2.setPriority(10);  // 设置t2优先级为最高级别
System.out.println(String.format("MyRunnable priority: %s", t2.getPriority()));  // MyRunnable priority: 10

t1.start();
t2.start();

7. 线程休眠

线程休眠是一种常见的线程控制机制,用于在指定的时间内暂停线程的执行。
可以通过 Thread.sleep(long millis) 方法实现线程休眠,该方法会使当前线程进入阻塞状态,直到休眠时间结束或被中断。

public static void test_thread_sleep() {
    // 测试线程静态方法 sleep
    Thread t = new MySleepThread();
    t.start();

    Date date = new Date();
    long time = date.getTime();

    // 定时任务
    Timer timer = new Timer();
    TimerTask task = new TimerTask() {
        @Override
        public void run() {
            Date date1 = new Date();
            long time1 = date1.getTime();
            long second = (time1 - time) / 1000;

            if (second >= 10) {
                timer.cancel();
                return;
            }

            System.out.println(String.format("MySleepThread: invoke %s second.", second));
        }
    };

    // 延迟0,每1秒执行一次
    timer.schedule(task, 0, 1000);

    /**
        * MySleepThread: start invoke.
        * MySleepThread: invoke 0 second.
        * MySleepThread: invoke 1 second.
        * MySleepThread: invoke 2 second.
        * MySleepThread: invoke 3 second.
        * MySleepThread: invoke 4 second.
        * MySleepThread: invoke 5 second.
        * MySleepThread: invoke 6 second.
        * MySleepThread: invoke 7 second.
        * MySleepThread: invoke 8 second.
        * MySleepThread: invoke 9 second.
        * MySleepThread: end invoke.
        */
}

static class MySleepThread extends Thread {
    @Override
    public void run() {
        super.run();

        System.out.println(String.format("MySleepThread: start invoke."));

        // 休眠 10 秒钟
        try {
            MySleepThread.sleep(10 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(String.format("MySleepThread: end invoke."));
    }
}

8. 线程挂起

早期版本中,Thread 类提供了 suspend() 方法用于暂停某个线程的执行,resume() 方法用于恢复被挂起的线程。

suspend()resume() 看似简单易用,但它们存在严重的缺陷,官方明确废弃了这些方法:
  • 死锁风险:如果线程在持有锁的情况下被挂起,它将不会释放该锁,其他需要该锁的线程将无限期等待,导致死锁。
  • 状态不一致:线程被挂起时,其内部状态可能处于不一致的状态,例如正在修改共享数据但未完成操作。
  • 误导性的线程状态:从线程状态的角度看,被挂起的线程仍显示为 Runnable,这会导致对系统当前状态的误判。
替代方案包括:
  • 使用标志位控制线程状态:通过共享变量控制线程的执行逻辑,例如使用 volatile 关键字确保变量的可见性。
  • 使用 wait() 和 notify():通过对象的监视器机制协调线程的执行,避免资源竞争和死锁问题。
  • 使用并发工具类:例如 java.util.concurrent 包中的 CountDownLatch、CyclicBarrier 或 Semaphore,这些工具类提供了更安全和高效的线程协调机制。
使用标志位控制线程状态

线程通过检查 paused 标志位来决定是否暂停执行。当需要恢复线程时,调用 resumeThread() 方法并唤醒等待的线程。

public class ControlledThread extends Thread {
    private volatile boolean paused = false;
    private final Object lock = new Object();

    public void run() {
        while (!isInterrupted()) {
            synchronized (lock) {
                while (paused) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
                // 执行任务
            }
        }
    }

    public void pauseThread() {
        paused = true;
    }

    public void resumeThread() {
        synchronized (lock) {
            paused = false;
            lock.notify();
        }
    }
}

9. 线程中断(终止)

安全的做法是使用中断机制,通过调用 interrupt() 方法来设置线程的中断状态,让线程在合适的时候自行退出。

thread.interrupt(); // 中断线程

使用布尔标志控制线程终止是一种有效且常见的做法,尤其是在无法依赖中断机制的情况下。
通过定义一个布尔变量作为线程的控制标志,线程可以在执行循环任务时定期检查该标志的状态,并在标志被设置为true时安全退出。
这种方式避免了强制终止线程所带来的资源泄漏和状态不一致问题,是一种协作式的线程终止方式。

public static void test_thread_interrupt() {
    Thread t = new MyInterruptThread();
    t.start();

    // 布尔变量终止线程(强烈推荐这种方式)
    t.run = false;
}

static class MyInterruptThread extends Thread {
    boolean run = true;

    @Override
    public void run() {
        super.run();

        for (int i = 0; i < 100; i++) {
            if (run) {
                System.out.println(String.format("MyCanStopThread: %s time invoke.", i));
            } else {
                // 终止线程
                return;
            }
        }
    }
}

10. 执行顺序 join

join() 是 Thread 类提供的一个方法,其核心作用是使调用该方法的线程(通常是主线程)等待目标线程执行完毕后再继续执行。
这种机制常用于线程间的同步控制,确保某些操作在依赖线程完成之后才执行。

public class Test_Thread_Join {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程T1执行: " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程T2执行: " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t1.start();
        t2.start();

        t1.join();  // 主线程等待t1完成
        t2.join();  // 主线程等待t2完成

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

    // 线程T2执行: 0
    // 线程T1执行: 0
    // 线程T2执行: 1
    // 线程T1执行: 1
    // 线程T1执行: 2
    // 线程T2执行: 2
    // 线程T1执行: 3
    // 线程T2执行: 3
    // 线程T1执行: 4
    // 线程T2执行: 4
    // 主线程继续执行
}

11. 暂停线程,回到就绪状态 yield

该方法并不强制调度器切换线程,因此不能用于精确控制线程执行顺序。
yield() 不会阻塞当前线程,也不会等待目标线程完成。

线程的 yield() 方法用于提示当前正在执行的线程主动释放 CPU 执行时间,使调度器有机会选择其他具有相同或更高优先级的线程运行。
该方法并不保证当前线程会立即停止执行,而是将调度权交还给操作系统,从而实现一种协作式调度机制。

Thread.yield() 是静态方法,调用时不会释放锁资源,仅影响当前线程的执行状态。
它适用于某些需要优化线程调度公平性的场景,例如希望多个线程交替执行以避免某个线程长时间占用 CPU 资源。

public class Test_Thread_Yield {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程T1: " + i);
                if (i == 2) {
                    Thread.yield();  // 提示T1让出CPU
                }
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程T2: " + i);
            }
        });

        t1.start();
        t2.start();
    }
    // 线程T2: 0
    // 线程T2: 1
    // 线程T2: 2
    // 线程T2: 3
    // 线程T1: 0
    // 线程T1: 1
    // 线程T1: 2
    // 线程T2: 4
    // 线程T1: 3
    // 线程T1: 4
}

12. 线程锁

一种用于控制多线程并发访问共享资源的机制,其核心作用是确保在同一时刻只有一个线程可以访问特定的代码段或数据结构,从而避免数据竞争和不一致的状态。

线程锁的实现主要包括 synchronized 关键字和 java.util.concurrent.locks.Lock 接口,它们提供了不同的方式来实现线程同步。

并发编程中,多个线程可能会同时访问和修改共享资源,如果没有适当的同步机制,就可能导致数据不一致、计算错误或程序崩溃。通过使用线程锁,可以确保每次只有一个线程能够访问共享资源,其他线程必须等待锁释放后才能继续执行。

synchronized
public class Test_Thread_Synchronized {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
        Test_Thread_Synchronized example = new Test_Thread_Synchronized();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("最终计数值: " + example.count);  // 最终计数值: 2000
    }
}
ReentrantLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test_Thread_ReentrantLock {
    private final Lock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Test_Thread_ReentrantLock example = new Test_Thread_ReentrantLock();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("最终计数值: " + example.count);  // 最终计数值: 2000
    }
}

13. 死锁

多个线程在执行过程中,因为争夺资源而陷入相互等待的状态,每个线程都持有部分资源,同时等待其他线程释放它所需要的资源,导致所有线程都无法继续执行。

这么说还是太抽象,看看下面这个例子:
(0)小明和小华是幼儿园同班同学,今天上手工课
(1)小明拿到了剪刀
(2)小华拿到了胶水
(3)小明想要拿胶水…(桌上没有,小华不给小明)
(4)小华想要要剪刀…(桌上没有,小明不给小华)
(5)陷入了僵持状态,也就是死锁

package com.lkm.test;

public class Test_DieLock {
    public static void main(String[] args) {
        new XiaoMing().start();
        new XiaoHua().start();
        // 小明拿到了剪刀
        // 小华拿到了胶水
        // 小明尝试获取胶水...
        // 小华尝试获取剪刀...
    }
}

class Resource {
    public static final Object scissors = new Object(); // 剪刀资源
    public static final Object glue = new Object();     // 胶水资源
}

class XiaoMing extends Thread {
    @Override
    public void run() {
        synchronized (Resource.scissors) {
            System.out.println("小明拿到了剪刀");
            try {
                Thread.sleep(1000); // 模拟处理时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("小明尝试获取胶水...");
            synchronized (Resource.glue) {
                System.out.println("小明成功拿到了胶水");
            }
        }
    }
}

class XiaoHua extends Thread {
    @Override
    public void run() {
        synchronized (Resource.glue) {
            System.out.println("小华拿到了胶水");
            try {
                Thread.sleep(1000); // 模拟处理时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("小华尝试获取剪刀...");
            synchronized (Resource.scissors) {
                System.out.println("小华成功拿到了剪刀");
            }
        }
    }
}
死锁的产生通常需要满足以下四个必要条件:
  • 互斥:资源不能共享,一次只能被一个线程持有。
  • 持有并等待:线程在等待其他资源时,不释放已持有的资源。
  • 不可抢占:资源只能由持有它的线程主动释放。
  • 循环等待:存在一个线程链,其中每个线程都在等待下一个线程所持有的资源。
死锁的解决方法
  1. 打破循环等待条件:通过统一资源获取顺序,避免线程之间形成循环等待。例如,所有线程按照相同的顺序请求资源,可以有效防止死锁发生。
  2. 使用超时机制:在尝试获取锁时设置超时时间,如果在指定时间内无法获取锁,则放弃当前操作并释放已持有的资源。这种方式可以避免线程无限期等待。
  3. 使用死锁检测工具:JVM 提供了死锁检测机制,可以通过 jstack 工具分析线程堆栈信息,定位死锁问题。此外,还可以使用 ThreadMXBean 编程检测死锁。
  4. 避免嵌套锁:尽量减少多个锁的嵌套使用,减少资源竞争的可能性。如果必须使用多个锁,应确保所有线程以相同的顺序获取锁。
  5. 使用可重入锁:Java 中的 synchronized 和 ReentrantLock 都支持可重入性,即一个线程可以多次获取同一个锁,从而避免了因重复加锁而导致的死锁问题。

14. 线程池 ThreadPoolExecutor

可以将线程池想象成一家快递公司的调度中心。快递公司有固定的快递员(线程),当有新的包裹(任务)需要派送时,调度中心会从可用的快递员中安排一个人去处理,而不是每来一个包裹就雇一个新的快递员。这样可以节省人力成本,同时提高整体效率 。

线程池是一种用于管理和复用线程的机制,其核心思想是将多个线程预先创建并维护在一个池中,当有任务需要执行时,从池中选取一个空闲线程来执行任务,任务执行完毕后线程不会被销毁,而是返回池中等待下一个任务。
这种方式避免了频繁创建和销毁线程所带来的性能开销,提高了系统的响应速度和资源利用率。

import java.util.concurrent.*;

public class Test_ThreadPoolExecutor {
    public static void main(String[] args) throws InterruptedException {
        /**
         * 线程池配置了 4 个核心线程、最多 8 个线程、任务队列容量为 10,并采用 CallerRunsPolicy 拒绝策略。
         * 任务提交后,线程池会根据当前线程数和队列状态动态分配线程资源。
         */
        ExecutorService pool = new ThreadPoolExecutor(
            4,  // 核心线程数
            8,  // 最大线程数
            30L, TimeUnit.SECONDS,  // 非核心线程空闲存活时间
            new ArrayBlockingQueue<>(10),  // 任务队列
            Executors.defaultThreadFactory(),  // 线程工厂
            new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略
        );

        for (int i = 1; i <= 20; i++) {
            final int taskId = i;
            pool.execute(() -> {
                System.out.println("任务 " + taskId + " 执行中,线程:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);  // 模拟任务耗时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        pool.shutdown();  // 关闭线程池,不再接受新任务
        pool.awaitTermination(1, TimeUnit.MINUTES);  // 等待所有任务执行完毕
        System.out.println("所有任务执行完毕");
    }

    // 任务 3 执行中,线程:pool-1-thread-3
    // 任务 2 执行中,线程:pool-1-thread-2
    // 任务 19 执行中,线程:main
    // 任务 4 执行中,线程:pool-1-thread-4
    // 任务 1 执行中,线程:pool-1-thread-1
    // 任务 15 执行中,线程:pool-1-thread-5
    // 任务 16 执行中,线程:pool-1-thread-6
    // 任务 18 执行中,线程:pool-1-thread-8
    // 任务 17 执行中,线程:pool-1-thread-7
    // 任务 20 执行中,线程:main
    // 任务 5 执行中,线程:pool-1-thread-5
    // 任务 10 执行中,线程:pool-1-thread-7
    // 任务 6 执行中,线程:pool-1-thread-6
    // 任务 8 执行中,线程:pool-1-thread-8
    // 任务 9 执行中,线程:pool-1-thread-2
    // 任务 12 执行中,线程:pool-1-thread-1
    // 任务 11 执行中,线程:pool-1-thread-3
    // 任务 7 执行中,线程:pool-1-thread-4
    // 任务 13 执行中,线程:pool-1-thread-7
    // 任务 14 执行中,线程:pool-1-thread-6
}
线程池的工作流程主要由以下几个关键组件协同完成:
  • 核心线程数(corePoolSize):线程池中长期维持的最小线程数量,即使这些线程处于空闲状态也不会被销毁。
  • 最大线程数(maximumPoolSize):线程池允许创建的最大线程数量,包括核心线程和非核心线程。
  • 任务队列(workQueue):用于存放等待执行的任务,常见的队列类型包括 ArrayBlockingQueue、LinkedBlockingQueue 和 SynchronousQueue。
  • 线程工厂(threadFactory):用于创建新线程的工厂类,可自定义线程的名称、优先级等属性。
  • 拒绝策略(handler):当任务队列已满且线程数量达到最大值时,线程池将采用拒绝策略处理新任务,例如抛出异常、调用者运行等。
线程池的执行流程如下:
  • 当提交一个新任务时,如果当前线程数小于核心线程数,线程池会创建新线程来执行任务。
  • 如果当前线程数等于或超过核心线程数,则任务会被放入任务队列中等待执行。
  • 如果任务队列已满,且当前线程数小于最大线程数,则线程池会创建非核心线程来执行任务。
  • 如果任务队列已满且线程数达到最大值,则线程池将根据拒绝策略处理新任务。
线程池的主要优势包括:
  • 降低资源开销:通过复用线程,减少线程创建和销毁的开销。
  • 提高响应速度:任务到达后可以立即执行,无需等待线程创建。
  • 控制并发资源:限制系统中并发线程的数量,防止资源耗尽或系统崩溃。
线程池广泛应用于以下场景:
  • Web 服务器和应用服务器:处理客户端请求,提高并发处理能力。
  • 异步任务处理:如日志记录、邮件发送、定时任务等。
  • 大数据处理:如并行计算、分布式任务调度等。

15. 定时和周期性任务执行的线程池 Executors.newScheduledThreadPool

Java 中用于创建支持定时和周期性任务执行的线程池的方法。该线程池内部使用 ScheduledThreadPoolExecutor 实现,继承自 ThreadPoolExecutor,具备调度能力。其核心特性包括支持延迟执行、周期性执行等操作。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Test_ScheduledThreadPool {
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

        // 延迟执行任务
        executor.schedule(() -> {
            System.out.println("任务 1 延迟 3 秒执行");
        }, 3, TimeUnit.SECONDS);

        // 周期性执行任务,固定频率(初始延迟 1 秒后,每 2 秒执行一次)
        executor.scheduleAtFixedRate(() -> {
            System.out.println("任务 2 每 2 秒执行一次");
        }, 1, 2, TimeUnit.SECONDS);

        // 周期性执行任务,固定延迟(执行完成后延迟 1 秒再执行下一次)
        executor.scheduleWithFixedDelay(() -> {
            System.out.println("任务 3 执行完成后延迟 1 秒再执行");
        }, 0, 1, TimeUnit.SECONDS);
    }
}
注意事项
  • 线程池大小设置:由于 newScheduledThreadPool 中的 maximumPoolSize 被设为 Integer.MAX_VALUE,理论上允许创建大量线程,但在实际使用中仍应合理设置 corePoolSize 以避免资源浪费或线程饥饿问题。
  • 任务队列特性:使用的 DelayedWorkQueue 是一个无界队列,支持延迟任务的排序和调度。但由于是无界队列,若任务提交速率远高于处理速率,可能导致内存溢出风险。
  • 任务异常处理:如果某个任务在执行过程中抛出异常,默认情况下线程池不会终止整个调度,但该任务的后续调度将不再执行。因此,建议在任务中加入异常捕获逻辑以保证调度的稳定性。
  • 关闭线程池:务必在程序结束前调用 shutdown() 或 shutdownNow() 方法,避免线程池中的线程持续运行导致程序无法正常退出。
适用场景

newScheduledThreadPool 适用于需要延迟执行或周期性执行的任务,例如:

  • 定时刷新缓存或配置
  • 定期执行日志清理或数据同步
  • 实现心跳检测机制
  • 周期性地从远程服务器拉取数据

在这些场景中,相较于传统的 Timer 类,ScheduledThreadPool 提供了更高的并发能力和更灵活的调度机制,同时具备更好的容错性和扩展性。

Logo

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

更多推荐