线程池相关

池化技术的出现是为了优化资源的使用,常见的池化技术有:线程池、JDBC连接池、内存池、对象池……
线程池的好处有:线程复用,管理线程,控制最大并发数
(假设线程池有5个线程,那么最大并发数为5,当然,还要考虑电脑的CPU是几核的)

传统方式创建线程

下面的代码例子是使用传统的方式创建10个线程,这样做的弊端是频繁的创建线程和销毁线程会导致非常大的系统开销;从代码的运行结果也可以看到每个线程都是不一样的,也就是线程用完即销毁,用时再创建,并没有实现复用

public static void main(String[] args) {
    for (int i = 1; i <= 10; i++) {
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + ":ok");
        }).start();
    }
}

在这里插入图片描述

线程池的三大方法
// 1. 单个线程,也就是创建的线程池中只有一个线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 2. 创建一个指定大小的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 3. 创建一个可伸缩的线程池,遇强则强,遇弱则弱;也就是我们需要用到多少个线程,这个线程池就有多少个线程
ExecutorService threadPool = Executors.newCachedThreadPool();

看第一个方法:

由于创建的线程池中只有一个线程,所以支持的最大并发数为1,因为每次都得等待上一个线程执行完毕后,归还到线程池,别的线程才有机会获取执行,由代码的运行结果也可以看到,每次执行的线程都是同一个

public static void main(String[] args) {
    // 单个线程,也就是创建的线程池中只有一个线程
    ExecutorService threadPool = Executors.newSingleThreadExecutor();
    for (int i = 1; i <= 10; i++) {
        threadPool.execute(()->{
            System.out.println(Thread.currentThread().getName() + ":ok");
        });
    }
    // 关闭线程池
    threadPool.shutdown();
}

在这里插入图片描述
看第二方法:

创建了一个指定大小为5的线程池,所以支持的最大并发数为5,也就是如果第一个线程在执行,又一个线程要执行,无需等待上一个线程执行完毕,可以直接从线程池中再拿一个线程运行,也就是多个线程可以同时运行;由代码的运行结果也可以看到,线程池中的5个线程都可能执行

public static void main(String[] args) {
    // 创建一个大小为5的线程池
    ExecutorService threadPool = Executors.newFixedThreadPool(5);
    for (int i = 1; i <= 10; i++) {
        threadPool.execute(()->{
            System.out.println(Thread.currentThread().getName() + ":ok");
        });
    }
    // 关闭线程池
    threadPool.shutdown();
}

在这里插入图片描述

看第三方法:

创建了一个可伸缩的线程池,线程池的大小会根据我们的需要用到的线程池数量去创建,由代码的运行结果也可以看到,确实使用了多个不同的线程,其中有三个线程使用的是同一个,这种现象是因为线程执行完毕后归还线程池,下一线程可能获取它再次执行

public static void main(String[] args) {
    // 创建一个可伸缩的线程池
    ExecutorService threadPool = Executors.newCachedThreadPool();
    for (int i = 1; i <= 10; i++) {
        threadPool.execute(()->{
            System.out.println(Thread.currentThread().getName() + ":ok");
        });
    }
    // 关闭线程池
    threadPool.shutdown();
}

在这里插入图片描述

线程池的七大参数

在了解线程池的七大参数之前,我们先看一下《阿里巴巴Java开发手册》对于创建线程池的规范约束:在这里插入图片描述
前面我们讲的三大方法都是使用了Executors这个工具类去创建线程的,但是阿里巴巴规范建议我们不这么做,所以我们应该使用原生的ThreadPoolExecutor去自定义创建线程,构造方法需要传递七个参数,这就是线程池的七大参数,追踪源码,其实线程池的三大方法最终也是去new ThreadPoolExecutor,只不过七个参数中有些是使用默认的
在这里插入图片描述通过银行办理业务理解七大参数:
在这里插入图片描述
对于银行办理业务,假设默认工作的窗口只有两个,这对应七大参数中的核心线程数为2;当两个人在办理业务,有第三个人进来了,那么它会进入阻塞队列,假设阻塞队列的长度为3,那么当阻塞队列满的时候,就会触发3 4 5这个三个窗口进行工作,也就是会触发七大参数中的非核心线程(非核心线程=最大线程数-核心线程数),当银行的五个窗口都满了,并且阻塞队列也满了,这时还有人进来的办理业务的话,就会采取拒绝策略,也就是对应七大参数中当所需要的线程数>最大线程数+阻塞队列的长度,这时候会触发拒绝策略,这里我们使用的是AbortPolicy策略,也就是当有第九个人进来的时候,不进行处理,并且抛出异常

我们通过代码来自定义一个线程池,对应的参数跟银行办理业务一致

public static void main(String[] args) {
    // 自定义线程池
    ExecutorService threadPool = new ThreadPoolExecutor(
            2,
            5,
            3,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(3),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());
    for (int i = 1; i <= 9; i++) {
        threadPool.execute(()->{
            System.out.println(Thread.currentThread().getName() + ":ok");
        });
    }
    // 关闭线程池
    threadPool.shutdown();
}
线程池的四种拒绝策略

拒绝策略接口RejectedExecutionHandler有四个实现类,代表四种不同的拒绝策略,默认使用的是 AbortPolicy
在这里插入图片描述

  • AbortPolicy:当触发拒绝策略时,不进行处理,抛出异常(终止策略,线程池会抛出异常并终止执行,它是默认的拒绝策略)
  • CallerRunsPolicy:哪来的去哪里,也就是当触发拒绝策略时,将不再是线程池去处理,一般是main线程去处理(把任务交给当前线程来执行)
  • DiscardPolicy:当触发拒绝策略时,丢掉任务,不会抛出异常(忽略此任务,指的是最新的任务)
  • DiscardOldestPolicy:当触发拒绝策略时,不会抛出异常,会尝试去和最早的线程竞争一下,如果最早的线程执行完毕,可以拿到这个线程,否则还是丢弃任务(忽略最早的任务,指的是最先加入队列的任务)
如何定义最大线程数

一般从CPU密集型和IO密集型进行考虑

① CPU密集型:指的是电脑的CPU是几核的,就设置最大线程数为几,这样可以保证CPU的效率最高,因为每台电脑的CPU核数都是不一样的,所以设置最大线程数应该通过代码动态设置,不应该写死

Runtime.getRuntime().availableProcessors(); // 获取CPU核数

②IO密集型:设置的最大线程数需要大于程序中非常耗IO的线程数

线程池的五种状态
  • RUNNING:能够接收新任务,以及对已添加的任务进行处理
  • SHUTDOWN:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务
  • STOP:不接收新任务,不处理已添加的任务,并且会中断正在处理的任务
  • TIDYING:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态
  • TERMINATED:线程池彻底终止,就变成TERMINATED状态
线程池的任务执行流程

在这里插入图片描述

ThreadPoolExecutor 的执行方法 excute 和 submit 的区别

execute() 和 submit() 都是用来执行线程池任务的,它们最主要的区别是,submit() 方法可以接收线程池执行的返回值,而 execute() 不能接收返回值。submit() 方法可以配合 Futrue 来接收线程执行的返回值以及取消线程任务等操作。它们的另一个区别是 execute() 方法属于 Executor 接口的方法,而 submit() 方法则是属于 ExecutorService 接口的方法。(ExecutorService 接口继承于Executor 接口)

单例模式创建线程池
public class Test {

    private static ThreadPoolExecutor threadPool;

    private static final AtomicInteger threadNumberAtomic = new AtomicInteger();
    
    /*** 
     * 单例模式获取线程池
     */
    public static ThreadPoolExecutor getThreadPool() {
        if (threadPool != null) {
            return threadPool;
        }
        synchronized (Test.class) {
            if (threadPool == null) {
                threadPool = new ThreadPoolExecutor(2, 2, 10000, TimeUnit.MILLISECONDS,
                    new ArrayBlockingQueue<>(10),
                    new ThreadFactory() {
                        @Override
                        public Thread newThread(Runnable r) {
                            Thread thread = new Thread(r);
                            thread.setName("thread-" + threadNumberAtomic.getAndIncrement());
                            return thread;
                        }
                    });
            }
            return threadPool;
        }
    }
}
在Java中,线程池是一种管理和复用线程的机制,可以提高多线程程序的效率和性能。以下是几个与Java线程池相关的最佳实践: 1. 使用Executors类创建线程池:Java提供了Executors类来创建不同类型的线程池。根据实际需求选择适当的线程池类型,如FixedThreadPool、CachedThreadPool、SingleThreadExecutor等。 2. 控制线程池的大小:线程池的大小应根据系统资源和任务特性进行调整。如果任务是CPU密集型的,线程数应与CPU核心数相近;如果是IO密集型的,可以适当增加线程数。 3. 设置合适的队列容量:线程池中的任务队列用于存放待执行的任务。如果任务量大于线程池处理能力,队列容量应设置得足够大,以避免任务丢失或拒绝执行。 4. 使用合适的拒绝策略:当线程池无法处理新提交的任务时,可以使用合适的拒绝策略来处理。常见的拒绝策略包括AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy和DiscardPolicy。 5. 谨慎使用无限线程池:CachedThreadPool是一个无限线程数的线程池,可以根据需要自动创建和回收线程。但过多的线程可能会导致系统负载过高,因此需要谨慎使用。 6. 使用合适的线程命名:为线程池中的线程设置有意义的名称,可以方便调试和日志记录。 7. 考虑任务的优先级:Java线程池支持设置任务的优先级。如果有任务需要优先执行,可以设置较高的优先级。 8. 及时关闭线程池:当不再需要线程池时,应及时关闭以释放资源。可以使用shutdown()方法平缓关闭线程池,或使用shutdownNow()方法立即关闭线程池。 以上是一些常见的Java线程池的最佳实践,根据具体场景和需求,可以进行适当调整和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值