【Java】线程池

一、核心原理

1)引入

我们之前写的多线程的代码是这样的:当我们需要一个线程的时候,我们就可以创建线程的对象,代码跑完,线程就会消失。这种方式其实是不对的,它会浪费操作系统的资源。

image-20240507110750721

所以我们也需要改进:准备一个容器用来存放线程,这个容器就叫做:线程池(ThreadPool)。

刚开始线程池里面是空的,没有线程。

当我们给线程池提交一个任务的时候,线程池本身它就会自动的去创建一个线程,拿着这个线程去执行任务,执行完了再把线程还回去。

第二次再提交任务的时候,它就不需要再去创建新的线程了,而是拿着已经存在的线程去执行任务,执行完了再还回去。

这个就是我们多线程的核心原理。


2)特殊情况

如果我们在提交第二个任务的时候,线程还正在执行第一个任务,它还没有还回去,此时线程池就会创建一个新的线程。

拿着新的线程去执行第二个任务,那么在这个时候,我又提交了很多其他的任务,此时线程池就会继续创建新的线程,执行新提交的任务。

任务执行完毕,它会把线程再还给线程池。

bs6n9-1qrxr

说道这里有的同学会有疑问:线程池它没有上线的吗?

线程池其实是有上线的,并且这个上线我们可以自己设置,假设我现在设置了最大线程数量为3。

那么这三个线程就会去执行前面的三个任务,后面的两个任务只能先排队等着。

image-20240507112023091

3)核心逻辑

接下来梳理一下线程池的核心逻辑。

1、创建一个池子,池子中是空的

2、提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子。
下回再次提交任务时,不需要创建新的线程,就不需要创建新的线程了,直接复用已经存在的线程即可。

3、但是如果提交任务时,池子中没有空闲线程,而且也无法创建新的线程,任务就会排队等待。


4)打码步骤

代码步骤其实也非常简单

1、创建线程池

2、提交任务

PS:当我们提交任务的时候,线程池的底层会去创建线程,或者去复用已经存在的线程,这些代码是不需要我们自己写的,是线程池的底层会去自动完成。我们要做的就是给它提交任务。

3、当所有的任务全部执行完毕,就可以关闭线程池

但是在我们实际开发中,线程池一般是不会关闭的。因为服务器是24小时运行的,服务器一旦不关闭,那就是随时随地都有可能有新的任务要执行,那么线程池也就不会关闭。

创建线程池在Java中有一个工具类:Executors,通过这个工具栏,我们就可以去调用方法,返回不同类型的线程池对象。

我们可以创建一个没有上线的线程池,但是这种方法并不是真正没有意义的上线,它也是有上线的,是int类型的最大值。

这个绝对是够用了,因为它还没有创建那么多线程,电脑就会先崩溃掉。因此我们会将它认为是一个没有上线的线程池。

static ExecutorService newCachedThreadPool()   创建一个默认的线程池

再往下,还会有第二种方式,它可以创建一个有上线的线程池。

static ExecutorService newFixedThreadPool(int nThreads)	    创建一个指定最多线程数量的线程池

二、代码实现

1)创建没有上线的线程池

//1.获取线程池对象,newCachedThreadPool()可以获取一个没有上限的线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();

提交任务的时候也是调用方法:submit()

其中,我们可以给它提供 Runnable的实现类,还可以给它提供 Callable的实现类

image-20240507114245967

因此此时我们需要写一个实现类

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}

测试类完整代码

//1.获取线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();

//2.提交任务
pool1.submit(new MyRunnable());

//3.销毁线程池,一旦池子被摧毁后,它里面所有的线程也会消失
pool1.shutdown();

运行代码,可以看见线程是有自己的名字 pool-thread-1,后面就是我们在 run() 中打印的序号。

image-20240507123643553

在刚刚我们说了,线程池一般是不会销毁的,因此需要将第三步注释掉,再给第二步多提交几个任务。

ExecutorService pool1 = Executors.newCachedThreadPool();
//2.提交任务
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());


//3.销毁线程池
//pool1.shutdown();

运行程序,可以看见编号为 1-5 ,由此可见线程池开启了五个线程去执行我们的任务。

image-20240507123835335

2)展示复用的效果

我们可以将任务写的简单一些,不要循环打印那么多次了,直接打印一句话就行了

public class MyRunnable implements Runnable{
    @Override
    public void run() {
            System.out.println(Thread.currentThread().getName() + "---");
    }
}

回到测试类中多提交几个任务,并且每次提交任务之前,都让 main线程 先睡个1秒钟,目的就是为了让上一个任务赶紧执行完毕,把线程还回去。

public static void main(String[] args) throws InterruptedException {
    ExecutorService pool1 = Executors.newCachedThreadPool();
    //2.提交任务
    pool1.submit(new MyRunnable());
    Thread.sleep(1000);
    pool1.submit(new MyRunnable());
    Thread.sleep(1000);
    pool1.submit(new MyRunnable());
    Thread.sleep(1000);
    pool1.submit(new MyRunnable());
    Thread.sleep(1000);
    pool1.submit(new MyRunnable());
    Thread.sleep(1000);
    pool1.submit(new MyRunnable());

    //3.销毁线程池
    //pool1.shutdown();
}

任务执行完毕,可以发现每次都是线程1

image-20240507124536220

3)创建有上限的线程池

static newFixedThreadPool(int nThreads)	    创建一个指定最多线程数量的线程池

测试代码

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}

-----------------------------
    
public static void main(String[] args) throws InterruptedException {
    //1.获取线程池对象
    ExecutorService pool1 = Executors.newFixedThreadPool(3);
    //2.提交任务
    pool1.submit(new MyRunnable());
    pool1.submit(new MyRunnable());
    pool1.submit(new MyRunnable());
    pool1.submit(new MyRunnable());
    pool1.submit(new MyRunnable());
    pool1.submit(new MyRunnable());

    //3.销毁线程池
    //pool1.shutdown();
}

运行完毕,不管怎么看,都只有 123,这就表示了线程池中最多只能有三个线程。

image-20240507124657817

以上是我们通过代码的运行结果进行的验证,还有一种验证方式:利用Debug进行验证

注意关注一下变量

  • pool size:目前线程池中有多少线程,现在是0,那就表示线程池刚开始创建的时候它里面是空的,什么也没有。
  • workQueue:记录了你当前有多少任务在排序等待,目前 size 是0,因此一开始的时候是没有认为在等的。

image-20240507125107772

点击下一步,当我们将第一个任务提交上去后, poll size 变成 1 了,表示线程池中已经有一个线程了。

image-20240507125206171

再点击两次下一步,线程池中里面就有三个了,此时看好了,池子里面有3个线程,后面的队伍还是0,还没有任务在排队。

image-20240507125302088

接下来再下一步,池子的长度还是 3,但是在外面已经有一个任务在排队等待了。

image-20240507125342919

再点击下一个,可以看见有两个任务在排队了。

image-20240507125412645

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值