【并发编程篇】源码分析,手动创建线程池

文章目录

  • 前言
    • Executors的三大方法
  • 简述线程池
  • 手动创建线程池
    • ⭐源码分析
    • ✨代码实现,手动创建线程池
      • CallerRunsPolicy()
      • AbortPolicy()
      • DiscardPolicy()
      • DiscardOldestPolicy()

【并发编程篇】源码分析,手动创建线程池_第1张图片

前言

Executors的三大方法

Java 中的 Executors 类提供了创建和管理线程池的工厂方法。主要有以下三种常用的静态工厂方法:

  • newFixedThreadPool(int nThreads):固定的线程池大小
    创建一个固定大小的线程池,其中包含指定数量的线程。如果所有线程都处于活动状态,并且任务队列已满,那么新任务将在任务队列中等待,直到有空闲的线程可用。
    【并发编程篇】源码分析,手动创建线程池_第2张图片

  • newCachedThreadPool():遇强则强,遇弱则弱
    创建一个可缓存的线程池,如果线程池的当前规模超过处理需求时,它会回收空闲线程;当需求增加时,则可以添加新的线程,线程池规模不存在限制。

【并发编程篇】源码分析,手动创建线程池_第3张图片

  • newSingleThreadExecutor(): 单个线程
    创建一个单线程的线程池,该线程池只有一个工作线程,它保证所有任务按顺序执行。

【并发编程篇】源码分析,手动创建线程池_第4张图片

这些工厂方法都返回实现了 ExecutorService 接口的 ThreadPoolExecutor 对象,这是 Java 线程池的一个具体实现,提供了对线程池的操作和控制。通过使用这些工厂方法创建线程池,可以方便快捷地满足不同场景下的线程管理需求。


【并发编程篇】源码分析,手动创建线程池_第5张图片
由于使用Executors不安全,那么我们需要手动创建一个线程池

简述线程池

线程池是一种用于管理和重用线程的机制,它通过预先创建一组线程,并将任务提交给这些线程来执行。线程池可以有效地控制并发线程的数量,避免了频繁创建和销毁线程的开销,提高了系统的性能和资源利用率。

线程池通常由以下组件构成:

  • 线程池管理器(ThreadPoolExecutor):负责创建、管理和调度线程池中的线程。它维护着一个线程池的状态,包括线程数量、任务队列、线程工厂等属性。
  • 工作线程(Worker Thread):线程池中的实际执行任务的线程。线程池会创建一定数量的工作线程,每个工作线程可以执行多个任务。
  • 任务队列(Task Queue):用于存储待执行的任务。当线程池中的线程空闲时,它们会从任务队列中获取任务并执行。
  • 线程工厂(Thread Factory):用于创建新的线程对象。线程池在需要创建新线程时,会使用线程工厂创建线程对象。

线程池的主要优点包括:

  • 提高系统性能:线程池可以控制并发线程的数量,避免了过多的线程竞争和频繁的线程创建销毁开销,提高了系统的性能。
  • 提高资源利用率:线程池可以重用线程,避免了线程创建和销毁的开销,提高了系统的资源利用率。
  • 提供线程管理和监控:线程池提供了对线程的管理和监控机制,可以方便地查看线程池的状态、线程执行情况等信息,便于调优和排查问题。

使用线程池时,需要根据具体的业务需求和系统资源情况合理配置线程池的大小、任务队列的容量以及其他相关参数,避免因线程池过小或过大导致的性能问题。

手动创建线程池

⭐源码分析

newSingleThreadExecutor的源码
【并发编程篇】源码分析,手动创建线程池_第6张图片newFixedThreadPool的源码
【并发编程篇】源码分析,手动创建线程池_第7张图片newCachedThreadPool的源码
【并发编程篇】源码分析,手动创建线程池_第8张图片其中有个参数长度是Integer,MAX_VALUE,长度过大,可能会堆积大量的请求,对应了上面我们说的不能用Executors,而要用ThreadPoolExecutors

我们发现,上面三者的源码中都有ThreadPoolExecutor
说明调用线程的本质调用的是ThreadPoolExecutor

下面我们来分析一下ThreadPoolExecutor
【并发编程篇】源码分析,手动创建线程池_第9张图片【并发编程篇】源码分析,手动创建线程池_第10张图片下面我们来想象一个场景,来更好的理解线程池
比如我们去银行办理业务
银行有5个窗口,但是只打开了2个(代表核心线程池的大小为2),还有3个没有开(触发最大并发量的情况下才会打开这3个窗口),还有容量为3的候客区(阻塞队列)
然后有用户去办理业务了,假如来了3个用户,2个去窗口了,还有1个在候客区等待,此时,又来了3个用户,这时候客区的位置不够了(阻塞队列满了),就要打开那关闭的3个窗口(触发最大并发量了)

✨代码实现,手动创建线程池

CallerRunsPolicy()

package org.Test6;

import java.util.concurrent.*;

public class Demo01 {
    public static void main(String[] args) {
        //自定义线程池
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,   //超时等待时间
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());
        try {
            for (int i = 1; i <= 10; i++) {
                //使用线程池后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"ok");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭线程池
            threadPool.shutdown();
        }
    }
}

【并发编程篇】源码分析,手动创建线程池_第11张图片

代码里面的超时等待时间是什么意思
我们对应上面去银行的场景,触发最大并发条件后,所有窗口都打开了,一段时间后,所有用户都离开了,过了设定的时间都没有业务,那么后面3个窗口就要关闭

AbortPolicy()

package org.Test6;

import java.util.concurrent.*;

public class Demo01 {
    public static void main(String[] args) {
        //自定义线程池
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,   //超时等待时间
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        try {
            for (int i = 1; i <= 10; i++) {
                //使用线程池后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"ok");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭线程池
            threadPool.shutdown();
        }
    }
}

【并发编程篇】源码分析,手动创建线程池_第12张图片

DiscardPolicy()

package org.Test6;

import java.util.concurrent.*;

public class Demo01 {
    public static void main(String[] args) {
        //自定义线程池
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,   //超时等待时间
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardPolicy()
        );
        try {
            for (int i = 1; i <= 10; i++) {
                //使用线程池后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"ok");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭线程池
            threadPool.shutdown();
        }
    }
}

丢掉任务,但是不会抛出异常

【并发编程篇】源码分析,手动创建线程池_第13张图片

DiscardOldestPolicy()

package org.Test6;

import java.util.concurrent.*;

public class Demo01 {
    public static void main(String[] args) {
        //自定义线程池
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,   //超时等待时间
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );
        try {
            for (int i = 1; i <= 10; i++) {
                //使用线程池后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"ok");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭线程池
            threadPool.shutdown();
        }
    }
}

【并发编程篇】源码分析,手动创建线程池_第14张图片

在技术的道路上,我们不断探索、不断前行,不断面对挑战、不断突破自我。科技的发展改变着世界,而我们作为技术人员,也在这个过程中书写着自己的篇章。让我们携手并进,共同努力,开创美好的未来!愿我们在科技的征途上不断奋进,创造出更加美好、更加智能的明天!

【并发编程篇】源码分析,手动创建线程池_第15张图片

你可能感兴趣的:(并发编程,开发语言)