Java多线程之线程池

目录

线程池是什么

线程池

用户态和内核态

线程池的优点

标准库中的线程池

创建线程池的方式

实现一个线程池

ThreadPoolExecutor

 拒绝策略

线程池是什么

线程池

多进程就是为了解决并发编程的问题,但是进程在重量级了,进程的创建和销毁都在消耗CPU资源了,于是就引入了线程,线程了就成为了并发编程的刚需,线程比进程要轻量很多,并且线程的创建和销毁也比进程要轻量很多,但是在某些场景,线程的创建和销毁要是很频繁,那么线程创建和销毁的开销也是非常大的。

为了解决这样的问题,我们就引出了线程池

我们已经了解了常量池,字符串连接池这些池子,下来就跟着我的节奏一起去揭开线程池的神秘面纱吧!!!

我们可以先理解为提前把线程在内核中创建好,等再次使用线程的时候就不是重新在系统中申请,而是在池子中拿线程,等某个线程不用了之后,就还给我们的线程池。这个操作就是比直接在系统中创建线程的方式要轻量很多。

为什么在线程池中使用拿线程要比直接在系统中创建和销毁线程要轻量呢?

要搞明白这个问题,我们需要了解用户态操作和内核态操作。

用户态和内核态

我们从线程池中拿线程,就是纯用户态的操作,但是在系统中创建线程,就是用户态和内核态之间的切换,真正的创建内核态操作完成的。

操作系统:内核+配套的应用程序。

内核:操作系统中核心的功能模块集合,比如:硬件管理,驱动管理,文件管理,进程管理,内存管理......    内核需要给上层的应用程序提供服务。

下来我们看一个具体的栗子来了解下为什么直接在操作系统中创建和申请线程要比在线程池中使用线程线程要重量级。

假如,我们去学校打印店里面去打印一个东西的时候,这时,打印店里面的人比较多,此时打印店老板给你说:同学,你是要在门口的自助打印机上自己打印,还是要等着让我给你打印,如果说我们是要自己在门口的自助打印机上自己打印,那么这个打印东西所消耗的时间完全是在我们可控的范围之内的,如果此时我们要是让老板给我们打印,那么打印完我们的东西需要多少时间就不是我们可以控制的了。要是此时打印东西的人比较多,可以需要的时间就很多,如果此时老板终于打印到你的东西了,正好此时,有一个人给老板打了个电话,此时老板接完电话之后突然找不到了,那么此时是不是很难受。

此时,这个打印操作就映射到了我们用户态和内核态中了。

如果是单纯的用户态操作,就和我们上面打印东西的一样,我们使用自助打印机,时间是可控的,让老板给我们打印东西,时间就不是我们可以控制的了。

因为我们在线程池中操作线程是用户态操作。此时开销就很小,所有在某些场景下,我们使用线程池的效率是非常高的。

线程池的优点

1:降低资源的消耗

2:提升响应速度

3:提高线程的可管理性

通过重复利用已经创建好的线程降低线程的创建和销毁造成的消耗。

当某个任务到达时,任务可以不等待线程的创建而直接开始执行。

线程是操作系统的稀缺资源,如果一直无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以把线程进行统一的管理和资源分配以及调度和监控。

标准库中的线程池

ExecutorService pool = Executors.newFixedThreadPool(10);  //创建10个线程的线程池

Executors中的静态方法newFixedThreadPool创建了10个线程的线程池。

返回值类型为ExecutorService   

通过返回的对象的中的submit方法就可以注册一个任务到线程池中。

//添加任务到线程池中
        pool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        });

创建线程池的方式

通过Executors类创建线程池的几种方式:

public static void main(String[] args) {
        //根据需求量创建线程,可灵活的收回空闲线程
        ExecutorService pool1 = Executors.newCachedThreadPool();
        //线程池中只有一个线程
        ExecutorService pool2 = Executors.newSingleThreadExecutor();
        //线程池中有10个线程
        ExecutorService pool3 = Executors.newFixedThreadPool(10);
        //设置延时时间后执行。
        ExecutorService pool4 = Executors.newScheduledThreadPool(1000);
    }

实现一个线程池

class MyThreadPool {
    //阻塞队列用于存放任务
    private BlockingQueue<Runnable> queue = new LinkedBlockingDeque<>();

    //把任务添加到阻塞队列中去
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

    //此处实现一个固定数量的线程池
    public MyThreadPool (int n) {
        for (int i = 0; i < n; i++) {  //连续创建N个线程
            Thread t = new Thread(() ->{

                try {
                    while (true) {
                        Runnable runnable = queue.take();  //把阻塞队列中的任务取出来
                        runnable.run();  //执行任务
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            //启动
            t.start();
        }
    }

}
public class threadDemo {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool = new MyThreadPool(10); //给线程池中创建10个线程
        for (int i = 0; i < 1000; i++) {
            int num = i;
            myThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello "+ num);
                }
            });
        }
    }
}

上述的ExectuorService和newFixedThreadPool都是对ThreadPoolExecutor进一步封装。

ThreadPoolExecutor是原装的线程池对象。

ThreadPoolExecutor

下面我们来看看ThreadPoolExecutor类的构造:

corePoolSize           核心线程数

maximumPoolSize 最大线程数

如果当前任务较多,线程池就会创建一些临时线程,如果当前空闲了,线程池就会把多出来的临时线程销毁

KeepAliveTime    保持存活时间

当任务少的时候,整体空闲的时候,临时线程不会被立即销毁,而是保持一定的时间,再去销毁。描述了临时线程最大存活时间。

unit    单位,存活时间的单位(毫秒,秒,分钟....)

BlockingQueue<Runnable> workQueue       线程池中有很多任务,也是通过阻塞队列来组织管理的。

我们可以手动给线程一个阻塞队列,此时就很方便的控制/获取队列中的信息了。submit方法就是把该任务放到队列中。

ThreadFactory threadfactory         线程工厂,用来创建线程。

RejectedExecutionHandler handler             线程池的拒绝策略,就是线程池中的任务已经满了,我们如何进行拒绝。

 拒绝策略

标准库提供了4中拒绝策略:

  1. 如果满了,继续添加任务,则直接抛出异常

  2. 添加的线程自己负责执行这个任务

  3. 丢弃最老的任务

  4. 丢弃最早的任务

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java多线程编程中,配置线程池的参数是非常重要的。根据不同的业务需求,可以设置线程池的参数来控制线程的数量和行为。根据引用中的例子,可以使用`Executors.newFixedThreadPool(int nThreads)`来创建一个固定线程数量的线程池。在这个方法中,`nThreads`参数表示线程池中的线程数量,只有这个数量的线程会被创建。然后,可以使用`pool.execute(Runnable command)`方法来提交任务给线程池执行。 在配置线程池时,需要考虑业务的性质。如果是CPU密集型的任务,比如加密、计算hash等,最佳线程数一般为CPU核心数的1-2倍。而如果是IO密集型的任务,比如读写数据库、文件、网络读写等,最佳线程数一般会大于CPU核心数很多倍。这样可以充分利用IO等待时间来执行其他任务,提高程序的性能。引用中给出了一些常见的线程池特点和构造方法参数。 总之,根据业务需求和特点,合理配置线程池的参数可以提高程序的性能和效率。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Java多线程线程池的参数和配置](https://blog.csdn.net/MRZHQ/article/details/129107342)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [Java多线程线程池(合理分配资源)](https://blog.csdn.net/m0_52861000/article/details/126869155)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Java多线程线程池](https://blog.csdn.net/weixin_53611788/article/details/129602719)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值