三大辅助类

CountDownLatch

CountDownLatch

CountDownLatch 是 Java 中的一个同步辅助类,它允许一个或多个线程等待其他线程完成操作。

构造函数

CountDownLatch(int count)

主要方法

  • void await() throws InterruptedException:使当前线程等待,直到计数器减为0。

  • boolean await(long timeout, TimeUnit unit) throws InterruptedException:使当前线程等待,直到计数器减为0或者超时。

  • void countDown():减少计数器的值。

  • long getCount():获取当前计数器的值。

示例代码

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);

        Runnable task = () -> {
            try {
                Thread.sleep(1000);
                System.out.println("Task completed");
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        for (int i = 0; i < 3; i++) {
            new Thread(task).start();
        }

        latch.await();
        System.out.println("All tasks completed");
    }
}

Cyclicbarrierr

CyclicBarrier

CyclicBarrier 是 Java 中的一个同步辅助类,允许一组线程相互等待,直到所有线程都到达某个共同的屏障点。它可以用于实现多线程的协作,确保所有线程在某个时刻一起执行。

构造函数

  • CyclicBarrier(int parties):创建一个新的 CyclicBarrier,指定参与者的数量。

  • CyclicBarrier(int parties, Runnable barrierAction):创建一个新的 CyclicBarrier,指定参与者的数量,并在所有参与者到达屏障时执行一个指定的操作。

主要方法

  • int await() throws InterruptedException, BrokenBarrierException:使当前线程等待,直到所有参与者都到达屏障点。

  • int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException:使当前线程等待,直到所有参与者到达屏障点或超时。

  • int getParties():获取参与者的数量。

  • int getNumberWaiting():获取当前正在等待的参与者数量。

  • boolean isBroken():检查屏障是否处于损坏状态。

  • void reset():重置屏障,使所有参与者回到初始状态。

示例代码

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        // 创建一个CyclicBarrier,指定参与者数量为3,并在所有线程到达时执行的操作
        CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("所有任务已完成,继续执行主线程"));

        Runnable task = () -> {
            try {
                // 模拟任务执行
                Thread.sleep((long) (Math.random() * 1000));
                System.out.println(Thread.currentThread().getName() + " 完成任务");
                
                // 等待其他线程到达
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        // 启动3个线程
        for (int i = 0; i < 3; i++) {
            new Thread(task).start();
        }
    }
}

代码解析

  1. CyclicBarrier的创建:在示例中,我们创建了一个 CyclicBarrier,它需要3个参与者,并在所有线程到达时执行一个打印操作。

  2. 任务执行:每个线程会随机睡眠一段时间,模拟任务的执行。

  3. 等待其他线程:每个线程在完成任务后调用 barrier.await(),等待其他线程到达屏障。

  4. 主线程继续执行:当所有线程都到达屏障时,执行指定的操作,输出“所有任务已完成,继续执行主线程”。

Semaphore

Semaphore 是 Java 中的一个同步辅助类,用于控制同时访问特定资源的线程数量。它维护一定数量的许可,线程需要获取许可才能执行,执行完毕后释放许可。

构造函数

  • Semaphore(int permits): 创建一个具有给定许可数的 Semaphore

主要方法

  • void acquire() throws InterruptedException: 获取一个许可,如果没有许可可用,则阻塞直到有许可为止。

  • void release(): 释放一个许可,将其返回给 Semaphore

  • int availablePermits(): 返回当前可用的许可数。

  • boolean tryAcquire(): 尝试获取一个许可,如果成功立即返回 true,否则返回 false

示例代码

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(2); // 创建一个许可数为2的Semaphore

        Runnable task = () -> {
            try {
                semaphore.acquire(); // 获取许可
                System.out.println(Thread.currentThread().getName() + " 获取许可");
                Thread.sleep(2000); // 模拟任务执行
                System.out.println(Thread.currentThread().getName() + " 释放许可");
                semaphore.release(); // 释放许可
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        // 启动5个线程
        for (int i = 0; i < 5; i++) {
            new Thread(task).start();
        }
    }
}

代码解析

  1. Semaphore的创建:在示例中,我们创建了一个许可数为2的 Semaphore,表示最多有2个线程可以同时执行任务。

  2. 任务执行:每个线程在执行任务前先尝试获取许可,如果许可数已满,则会阻塞直到有许可可用。

  3. 释放许可:每个线程执行完任务后会释放许可,让其他线程可以获取许可继续执行。

  4. 控制并发数量:通过 Semaphore,我们可以灵活控制同时执行任务的线程数量,实现资源的合理利用。

读写锁ReadWriteLock

读写锁 ReadWriteLock

读写锁(ReadWriteLock)是一种特殊的锁机制,用于控制对共享资源的访问。它允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。读写锁可以提高并发性能,适用于读多写少的场景。

构造函数

  • ReadWriteLock readWriteLock = new ReentrantReadWriteLock(): 创建一个读写锁实例。

主要方法

  • Lock readLock = readWriteLock.readLock(): 获取读锁实例。

  • Lock writeLock = readWriteLock.writeLock(): 获取写锁实例。

  • void lock(): 获取锁,如果锁不可用则阻塞。

  • void unlock(): 释放锁。

示例代码

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private static int count = 0;
    private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        Runnable readTask = () -> {
            readWriteLock.readLock().lock(); // 获取读锁
            try {
                System.out.println("Read value: " + count);
            } finally {
                readWriteLock.readLock().unlock(); // 释放读锁
            }
        };

        Runnable writeTask = () -> {
            readWriteLock.writeLock().lock(); // 获取写锁
            try {
                count++; // 修改共享资源
                System.out.println("Write value: " + count);
            } finally {
                readWriteLock.writeLock().unlock(); // 释放写锁
            }
        };

        // 启动5个读线程
        for (int i = 0; i < 5; i++) {
            new Thread(readTask).start();
        }

        // 启动2个写线程
        for (int i = 0; i < 2; i++) {
            new Thread(writeTask).start();
        }
    }
}

代码解析

  1. 创建一个 ReadWriteLock 实例。

  2. 定义一个共享资源 count,初始值为0。

  3. 定义一个读任务 readTask,获取读锁,读取共享资源的值,并释放读锁。

  4. 定义一个写任务 writeTask,获取写锁,修改共享资源的值,并释放写锁。

  5. 启动5个读线程,每个线程读取共享资源的值。

  6. 启动2个写线程,每个线程修改共享资源的值。

通过读写锁,我们可以实现多个线程同时读取共享资源,提高并发性能。同时,读写锁保证了写操作的原子性,只允许一个线程写入共享资源,避免了数据不一致的问题。

独占锁和共享锁

独占锁(Exclusive Lock)

独占锁是一种锁机制,也称为排他锁,它允许只有一个线程对共享资源进行访问,其他线程必须等待该线程释放锁之后才能访问共享资源。独占锁适用于需要独占资源进行操作的场景,例如写操作。

示例代码

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ExclusiveLockExample {
    private static int count = 0;
    private static Lock exclusiveLock = new ReentrantLock();

    public static void main(String[] args) {
        Runnable task = () -> {
            exclusiveLock.lock(); // 获取独占锁
            try {
                count++; // 修改共享资源
                System.out.println("Modified value: " + count);
            } finally {
                exclusiveLock.unlock(); // 释放独占锁
            }
        };

        // 启动3个线程
        for (int i = 0; i < 3; i++) {
            new Thread(task).start();
        }
    }
}

代码解析

  1. 创建一个 ReentrantLock 实例作为独占锁。

  2. 定义一个共享资源 count,初始值为0。

  3. 定义一个任务 task,获取独占锁,修改共享资源的值,并释放独占锁。

  4. 启动3个线程,每个线程对共享资源进行修改操作。

通过独占锁,我们可以确保在任意时刻只有一个线程可以修改共享资源,避免了多个线程同时修改导致的数据不一致问题。

共享锁(Shared Lock)

共享锁是一种锁机制,它允许多个线程同时对共享资源进行读取操作,但在有线程对资源进行写操作时,不允许其他线程进行读取操作。共享锁适用于读多写少的场景,可以提高并发性能。

示例代码

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class SharedLockExample {
    private static int count = 0;
    private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        Runnable readTask = () -> {
            readWriteLock.readLock().lock(); // 获取共享读锁
            try {
                System.out.println("Read value: " + count);
            } finally {
                readWriteLock.readLock().unlock(); // 释放共享读锁
            }
        };

        Runnable writeTask = () -> {
            readWriteLock.writeLock().lock(); // 获取独占写锁
            try {
                count++; // 修改共享资源
                System.out.println("Write value: " + count);
            } finally {
                readWriteLock.writeLock().unlock(); // 释放独占写锁
            }
        };

        // 启动3个读线程
        for (int i = 0; i < 3; i++) {
            new Thread(readTask).start();
        }

        // 启动2个写线程
        for (int i = 0; i < 2; i++) {
            new Thread(writeTask).start();
        }
    }
}

代码解析

  1. 创建一个 ReentrantReadWriteLock 实例作为读写锁。

  2. 定义一个共享资源 count,初始值为0。

  3. 定义一个读任务 readTask,获取共享读锁,读取共享资源的值,并释放共享读锁。

  4. 定义一个写任务 writeTask,获取独占写锁,修改共享资源的值,并释放独占写锁。

  5. 启动3个读线程,每个线程对共享资源进行读取操作。

  6. 启动2个写线程,每个线程对共享资源进行修改操作。

通过共享锁,我们可以实现多个线程同时读取共享资源,提高并发性能,同时保证了写操作的原子性,避免了数据不一致的问题。

阻塞队列

阻塞队列(Blocking Queue)是一种特殊的队列,它支持在队列为空时进行获取操作的线程阻塞,以及在队列已满时进行插入操作的线程阻塞。阻塞队列常用于生产者-消费者模式中,能够有效地协调生产者和消费者的速度,实现线程间的同步。

阻塞队列的主要 API 包括:

  • put(E e): 将元素放入队列,如果队列已满则阻塞。

  • take(): 从队列中取出元素,如果队列为空则阻塞。

  • offer(E e, long timeout, TimeUnit unit): 将元素放入队列,如果队列已满则等待指定时间后返回。

  • poll(long timeout, TimeUnit unit): 从队列中取出元素,如果队列为空则等待指定时间后返回。

  • add(E e): 将元素放入队列,如果队列已满则抛出异常。

  • remove(): 从队列中取出元素,如果队列为空则抛出异常。

  • element(): 获取队列头部的元素,如果队列为空则抛出异常。

  • peek(): 获取队列头部的元素,如果队列为空则返回 null。

  • offer(): 将元素放入队列,有返回值不抛出异常,如果队列已满则返回false

  • poll(): 从队列中取出元素,有返回值不抛出异常,如果队列为空则返回 null

示例代码如下:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class BlockingQueueExample {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5); // 创建一个容量为5的阻塞队列

        Runnable producer = () -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    queue.put(i); // 将元素放入队列,如果队列已满则阻塞
                    System.out.println("Produced: " + i);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Runnable consumer = () -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    int value = queue.take(); // 从队列中取出元素,如果队列为空则阻塞
                    System.out.println("Consumed: " + value);
                    Thread.sleep(1500);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        new Thread(producer).start(); // 启动生产者线程
        new Thread(consumer).start(); // 启动消费者线程
    }
}

在示例代码中,我们使用 ArrayBlockingQueue 创建了一个容量为5的阻塞队列。生产者线程不断向队列中放入元素,如果队列已满则会阻塞;消费者线程不断从队列中取出元素,如果队列为空则会阻塞。这样就实现了生产者和消费者之间的协调。

SynchronousQueue 同步队列

SynchronousQueue

SynchronousQueue 是 Java 中的一个特殊的阻塞队列,它的容量为0,即每个插入操作必须等待一个相应的删除操作,反之亦然。它主要用于线程间的数据交换,例如生产者-消费者模式中的数据传递。

构造函数

  • SynchronousQueue(): 创建一个不存储元素的 SynchronousQueue

主要方法

  • put(E e): 将指定的元素插入此队列,如果没有消费者线程等待接收,则阻塞等待。

  • take(): 从队列中取出元素,如果没有生产者线程等待传递元素,则阻塞等待。

  • offer(E e, long timeout, TimeUnit unit): 将指定的元素插入此队列,如果没有消费者线程等待接收,则等待指定时间后返回。

  • poll(long timeout, TimeUnit unit): 从队列中取出元素,如果没有生产者线程等待传递元素,则等待指定时间后返回。

示例代码

import java.util.concurrent.SynchronousQueue;

public class SynchronousQueueExample {
    public static void main(String[] args) {
        SynchronousQueue<Integer> queue = new SynchronousQueue<>();

        Runnable producer = () -> {
            try {
                queue.put(1); // 将元素放入队列,如果没有消费者线程等待接收,则阻塞等待
                System.out.println("Produced: 1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Runnable consumer = () -> {
            try {
                int value = queue.take(); // 从队列中取出元素,如果没有生产者线程等待传递元素,则阻塞等待
                System.out.println("Consumed: " + value);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        new Thread(producer).start(); // 启动生产者线程
        new Thread(consumer).start(); // 启动消费者线程
    }
}

代码解析

  1. 创建一个 SynchronousQueue 实例,用于生产者和消费者线程之间的数据交换。

  2. 定义一个生产者任务 producer,将元素放入队列,如果没有消费者线程等待接收,则阻塞等待。

  3. 定义一个消费者任务 consumer,从队列中取出元素,如果没有生产者线程等待传递元素,则阻塞等待。

  4. 启动生产者线程和消费者线程,进行数据交换操作。

通过 SynchronousQueue,我们可以实现线程间的数据交换,确保生产者和消费者线程之间的同步。

线程池

池化技术

池化技术概述

池化技术是一种常见的资源管理技术,通过预先创建一定数量的资源池,在需要时从池中获取资源,并在使用完毕后将资源归还到池中,以提高资源的重复利用率和系统性能。池化技术通常应用于数据库连接池、线程池、对象池等场景。

池化技术优势

  1. 资源重复利用:池化技术可以避免频繁地创建和销毁资源,提高资源的重复利用率,减少资源的浪费。

  2. 性能优化:通过池化技术,可以减少资源的创建和销毁开销,降低系统负担,提高系统性能和响应速度。

  3. 资源控制:池化技术可以限制资源的数量,防止资源过度消耗,避免系统资源耗尽导致系统崩溃。

  4. 提高并发能力:池化技术可以有效管理资源的并发访问,避免资源竞争和线程阻塞,提高系统的并发能力。

池化技术应用场景

  1. 数据库连接池:在数据库访问中,通过数据库连接池管理数据库连接,避免频繁地创建和关闭数据库连接,提高数据库访问效率。

  2. 线程池:通过线程池管理线程资源,控制并发线程数量,避免线程频繁创建和销毁带来的性能开销。

  3. 对象池:管理对象的创建和销毁,例如连接对象、缓存对象等,提高对象的重复利用率,降低系统开销。

  4. 连接池:管理网络连接资源,如HTTP连接池、FTP连接池等,减少连接建立和断开的开销,提高网络通信效率。

  5. 资源池:管理各种资源的池化,如文件资源、内存资源等,提高资源的利用率和系统性能。

池化技术实现方式

  1. 对象池:预先创建一定数量的对象,通过对象池管理对象的获取和释放。

  2. 连接池:维护一定数量的连接,通过连接池管理连接的获取和释放。

  3. 线程池:管理一定数量的线程,通过线程池管理线程的执行和复用。

  4. 缓存池:管理缓存对象,通过缓存池管理缓存的存储和访问。

  5. 资源池:管理各种资源的池化,通过资源池管理资源的获取和释放。

池化技术实现步骤

  1. 初始化池:创建一定数量的资源,加入到池中。

  2. 资源获取:从池中获取资源,如果池中无可用资源,则等待或创建新资源。

  3. 资源使用:使用获取到的资源进行操作。

  4. 资源归还:在资源使用完毕后,将资源归还到池中,以便其他线程继续使用。

  5. 资源销毁:在池不再需要时,销毁池中的资源,释放资源占用的内存。

总结

池化技术是一种重要的资源管理技术,通过预先创建资源池,有效管理资源的获取和释放,提高资源的重复利用率和系统性能。在实际开发中,合理应用池化技术可以优化系统架构,提升系统的稳定性和性能。

Executors 三大方法

newFixedThreadPool

newFixedThreadPool 方法返回一个固定大小的线程池,该线程池中的线程数量始终保持不变。当有新的任务提交时,如果线程池中有空闲线程,则立即执行;如果线程池中没有空闲线程,则任务将被放入任务队列中等待执行。

方法签名

public static ExecutorService newFixedThreadPool(int nThreads)

示例代码

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3); // 创建一个固定大小为3的线程池

        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                System.out.println("Thread " + Thread.currentThread().getName() + " is executing task");
            });
        }

        executor.shutdown(); // 关闭线程池
    }
}

newCachedThreadPool

newCachedThreadPool 方法返回一个可根据实际情况调整线程数量的线程池。当有新的任务提交时,如果线程池中有空闲线程,则立即执行;如果线程池中没有空闲线程,则创建新的线程执行任务。当线程空闲时间超过60秒,将被回收。

方法签名

public static ExecutorService newCachedThreadPool()

示例代码

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool(); // 创建一个可根据实际情况调整线程数量的线程池

        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                System.out.println("Thread " + Thread.currentThread().getName() + " is executing task");
            });
        }

        executor.shutdown(); // 关闭线程池
    }
}

newSingleThreadExecutor

newSingleThreadExecutor 方法返回一个只有一个线程的线程池,保证所有任务按照指定顺序执行。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

方法签名

public static ExecutorService newSingleThreadExecutor()

示例代码

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExecutorExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor(); // 创建一个只有一个线程的线程池

        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                System.out.println("Thread " + Thread.currentThread().getName() + " is executing task");
            });
        }

        executor.shutdown(); // 关闭线程池
    }
}

通过 Executors 类提供的这三种线程池,可以方便地创建不同类型的线程池,满足不同的业务需求。

ThreadPoolExecutor

ThreadPoolExecutor 是 Java 中用于管理线程池的类,通过它可以方便地创建和管理线程池,控制线程的数量、任务的执行以及线程池的行为。ThreadPoolExecutor 提供了丰富的参数和方法,可以根据需求灵活地配置线程池的各项属性。

七大参数

  1. corePoolSize:核心线程数,线程池中始终保持的线程数量,即使线程处于空闲状态。当提交任务时,如果当前线程数小于 corePoolSize,则会创建新线程来处理任务,即使有空闲线程可用。如果线程数大于 corePoolSize,则任务会被放入任务队列等待执行。

  2. maximumPoolSize:最大线程数,线程池中允许的最大线程数量。当任务队列已满且当前线程数小于 maximumPoolSize 时,会创建新线程来处理任务。超过最大线程数的任务会根据拒绝策略进行处理。

  3. keepAliveTime:线程空闲时间,当线程数大于 corePoolSize 时,多余的空闲线程会在指定时间内被回收,直到线程数等于 corePoolSize。这样可以节省系统资源。

  4. unit:空闲时间的单位,通常与 keepAliveTime 一起使用,表示空闲时间的单位,如 TimeUnit.SECONDS

  5. workQueue:任务队列,用于存储等待执行的任务。ThreadPoolExecutor 提供了多种任务队列类型,如 ArrayBlockingQueueLinkedBlockingQueueSynchronousQueue 等,可以根据需求选择合适的队列类型。

  6. threadFactory:线程工厂,用于创建新线程。可以自定义线程工厂来设置线程的名称、优先级等属性。

  7. handler:拒绝策略,当任务无法被执行时的处理策略。常见的拒绝策略包括 AbortPolicy(默认策略,抛出异常)、CallerRunsPolicy(由调用线程执行任务)、DiscardPolicy(丢弃任务)和 DiscardOldestPolicy(丢弃队列头部任务)。

通过合理配置这七大参数,可以根据实际需求创建出高效、稳定的线程池,提高系统的并发处理能力。

示例代码

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExecutorExample {
    public static void main(String[] args) {
        int corePoolSize = 2;
        int maximumPoolSize = 5;
        long keepAliveTime = 5000;
        TimeUnit unit = TimeUnit.MILLISECONDS;
        ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);

        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);

        // 提交任务
        for (int i = 0; i < 10; i++) {
            executor.execute(() -> System.out.println("Task executed by " + Thread.currentThread().getName()));
        }

        // 关闭线程池
        executor.shutdown();
    }
}

在上面的示例中,我们创建了一个 ThreadPoolExecutor,设置了核心线程数为2,最大线程数为5,线程空闲时间为5秒,任务队列为容量为10的 ArrayBlockingQueue。然后提交了10个任务给线程池执行,并最终关闭了线程池。

四种拒绝策略

在使用线程池时,当任务数量超过线程池的处理能力时,可能会出现拒绝任务的情况。Java 提供了四种拒绝策略来处理这种情况,具体如下:

1. AbortPolicy

描述

这是默认的拒绝策略。当线程池无法处理新任务时,它会抛出 RejectedExecutionException。这种策略适用于对任务处理有严格要求的场景。

示例代码
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class AbortPolicyExample {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(2), new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                System.out.println("Task executed by " + Thread.currentThread().getName());
            });
        }
    }
}

2. CallerRunsPolicy

描述

当线程池无法处理新任务时,调用者线程会直接执行该任务。这样可以减轻线程池的压力,适用于对任务处理有一定灵活性的场景。

示例代码
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CallerRunsPolicyExample {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(2), new ThreadPoolExecutor.CallerRunsPolicy());

        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                System.out.println("Task executed by " + Thread.currentThread().getName());
            });
        }
    }
}

3. DiscardPolicy

描述

当线程池无法处理新任务时,直接丢弃该任务,不抛出异常。这种策略适用于对任务丢失不敏感的场景。

示例代码
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DiscardPolicyExample {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(2), new ThreadPoolExecutor.DiscardPolicy());

        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                System.out.println("Task executed by " + Thread.currentThread().getName());
            });
        }
    }
}

4. DiscardOldestPolicy

描述

当线程池无法处理新任务时,丢弃最旧的任务,并尝试提交当前任务。这种策略适用于希望保留最新任务的场景。

示例代码
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DiscardOldestPolicyExample {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(2), new ThreadPoolExecutor.DiscardOldestPolicy());

        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                System.out.println("Task executed by " + Thread.currentThread().getName());
            });
        }
    }
}

总结

选择合适的拒绝策略对于线程池的性能和稳定性至关重要。根据具体的业务需求和场景,合理配置拒绝策略可以有效地管理任务的执行和资源的利用。

线程池选择建议

在 Java 中,线程池是一种重要的并发编程工具,它可以管理和重用线程,提高程序的性能和稳定性。在选择线程池时,通常推荐使用 ThreadPoolExecutor 而不是 Executors 工厂类来创建线程池,主要有以下几个原因:

  1. 灵活性ThreadPoolExecutor 提供了更多的参数配置选项,可以根据实际需求进行灵活调整,如核心线程数、最大线程数、线程存活时间、任务队列类型等,而 Executors 工厂类提供的方法具有固定的配置,无法满足所有场景的需求。

  2. 避免资源耗尽Executors 工厂类中的一些静态方法会使用默认的配置,如 newFixedThreadPool 会使用 LinkedBlockingQueue 作为任务队列,如果任务过多,可能会导致内存溢出。而使用 ThreadPoolExecutor 可以根据实际情况选择合适的队列类型,避免资源耗尽问题。

  3. 任务拒绝策略ThreadPoolExecutor 允许自定义任务拒绝策略,当线程池无法处理新任务时的处理方式,如抛出异常、丢弃任务、阻塞调用者等。而 Executors 工厂类提供的方法中无法直接指定任务拒绝策略。

  4. 线程池扩展性:通过继承 ThreadPoolExecutor 类,可以实现自定义的线程池,满足特定需求。而 Executors 工厂类提供的方法无法实现这种扩展性。

阿里java手册解释:

  • FixedThreadPoolExecutor和SingleThreadExecutor可能会因为大量请求堆积而造成OOM

  • CacheThreadPoolExecutor 可能会因为大量创建线程而造成OOM

最大线程(池的最大的大小)到底该如何定义

cpu密集型:cpu是几核就是几

IO密集型 判断程序中IO消耗较多的线程数,大于即可