多线程实例4--线程池

线程池


之前学过String字符串常量池 MySQL的JDBC中的数据库连接池(DataSource),这些都是池的概念

"池"可以节省对象重复创建和初始化锁耗费的时间,对于哪些需要经常被系统频繁请求和调用的对象,使用池可以提高运行的效率

进程是比较重量级的,导致创建和销毁进程是比较抵消的(内存 文件资源的申请和释放),所以有了线程,线程可以共享内存 文件资源,新的线程可以复用之前线程的资源,不用再申请资源,所以就快了

但是如果线程创建的速率进一步频繁了,线程的创建和销毁的开销也是不能忽略的,所以此时就要使用线程池来进一步优化速度了

在线程池中,会有很多的线程,需要执行任务的时候, 就不需要创建线程了,直接从线程池中去一个现成的线程.直接使用, 用完了也不要释放线程,而是还到线程池中就行

有一个问题: 为什么从线程池中取一个线程要比直接创建一个线程更快?

创建线程是要在操作系统内核中完成的, 涉及到用户态–>内核态, 这是有一定的开销的

image-20220923205645963

由应用程序发起创建线程的行为,线程本质上是PCB, 这是操作系统内核的数据结构, 应用程序需要通过系统调用进入到内核中执行指令 内核完成PCB的创建,把PCB加入到调度队列中, 再返回给应用程序

总之, 创建线程 是 用户态–>内核态 共同完成的逻辑

从线程池里面取线程只是用户态中发生的逻辑

每个进程都有一个用户态,可是内核态只有一个,所以创建线程的速度就会更慢

所以从线程池中去线程 的效率高于 创建一个新的线程

使用标准库中的线程池

public static void main(String[] args) {
    ExecutorService pool = Executors.newCachedThreadPool();
    //使用工厂模式创建线程池
}

这里的创建线程池并没有显式地使用new , 而是通过Executors的newCacheThreadPool 来创建先线程池, 这里就用到了工厂模式

package Threading;

import javax.crypto.spec.PSource;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo27 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();//使用工厂模式创建线程池
        pool.submit(new MyRunnable(){
            @Override
            public void run() {
                System.out.println("开始运行!");
            }
        });
    }
}

线程池的实现

一个线程池中可以通过提交N个任务, 对应的线程池中有M个线程负责完成这N个任务,那么该如何把这N个任务分配给M个线程呢?

实现思路: 使用生产者消费者模型,高搞一个阻塞队列, 每个任务都放到阻塞队列中, 让M个线程来取队列元素, 如果队列空了,就阻塞等待

如果队列不为空,每个线程执行完任务继续来取队列,直到队列变空,阻塞等待

package Threading;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;


class MyThreadPool {
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);//把任务放进阻塞队列中
    }
    //构造方法
    public MyThreadPool(int m) {
        //创建m个线程
        for(int i = 0;i < m; i++){
            Thread t = new Thread(()->{
               while(true){
                   try {
                      Runnable runnable = queue.take();//取出任务
                       runnable.run();//运行任务
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
            });
            t.start();
        }
    }
}
public class Demo29 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool pool = new MyThreadPool(10);//一共有10个线程
        for(int i = 0;i < 100; i++){
            int taskId = i;
            pool.submit(new Runnable() {  //放进阻塞队列
                @Override
                public void run() {
                    System.out.println("开始执行任务" + taskId + "  当前线程" + Thread.currentThread().getName());
                }
            });
        }
    }
}

其实线程池的实现是不难的,只要确定了阻塞对立, 理清楚逻辑关系就行了

image-20220924101136791

线程池中的ThreadPoolExecutor 的构造方法

image-20220926191312642

主要是看最后一行的参数

corePoolSize : 线程池保有的最小线程数

maximumPoolSize: 线程池中有的最大线程数

系统资源是有限的,所以不能允许有太多的线程,但是线程 也不能 太少,所以有了corePoolSize和maximumPoolSize

KeepAliveTime 和 unit

当线程闲置的时候,保持 线程存活的时间

workQueue : 手动给线程池传入一个任务队列, 任务队本来 也有自己的队列,要是不手动传入 ,线程池也会自己创建一个任务队列

threadFactory : 描述了线程是如何创建的,工厂对象负责创建线程,所以程序员可以自己手动指定线程创建的策略,一般都是默认的

重点*

RejectedExecutionHandler Handler : 拒接策略 当线程池里面的工作线程已经忙不过来了,此时 还要加新的任务, 那么就会执行拒绝策略

一共有四种拒绝策略:

image-20220926193627195

第一种策略: 丢弃所有的任务,并且抛出异常

ThreadPoolExecutor.AbortPolicy  //中断策略
A handler[处理策略] for rejected tasks that throws a RejectedExecutionException.

第二种策略: 由调用线程处理任务

ThreadPoolExecutor.CallerRunsPolicy
A handler for rejected tasks that runs the rejected task directly 
in the calling thread of the `execute` method, unless the executor[调用线程] 
has been shut down, in which case the task is discarded[丢弃].

第三种策略: 丢弃最老的任务

ThreadPoolExecutor.DiscardOldestPolicy
A handler for rejected tasks that discards the oldest unhandled request and then
retries `execute`, unless the executor is shut down,
in which case the task is discarded.//丢弃最老的不能完成的任务,并尝试处理新的任务

第四种策略: 丢弃最新任务,而且不用抛异常

ThreadPoolExecutor.DiscardPolicy
A handler for rejected tasks that silently discards the rejected task.

对于ThreadPoolEXecutor来说,拒绝策略是很重要的

线程池可以自定义线程数目,那么应该设置多少个线程?该怎么确定?

首先,这是无法直接给出一个具体的数字的,也没法给出一个具体的比例关系

这主要取决于 1 主机CPU的配置 , 2 实际程序的执行特点 这两个都是不确定的

程序的执行特点: 主要有 CPU密集型(需要做大量的算术运算和逻辑判断) IO密集型(需要做大量的读写网卡/读写硬盘),还有程序既是CPU密集型也是IO密集型

所以要想知道哪种是最好的线程数,还得多试几个线程数目,试一下性能,才能知道该设置多少个线程数

描述一下线程池的执行流程和拒绝策略

线程池的执行流程:

  1. 当新加入一个任务时,先判断当前线程数是否大于核心线程数,如果结果为 false,则新建核心线程并执行任务;
  2. 如果结果为 true,则判断任务队列是否已满,如果结果为 false,则把任务添加到任务队列中等待线程执行
  3. 如果结果为 true,则判断当前线程数量是否超过最大线程数?如果结果为 false,则新建线程执行此任务
  4. 如果结果为 true,执行拒绝策略。

拒绝策略:

  1. AbortPolicy:中止策略,线程池会抛出异常并中止执行此任务;

  2. CallerRunsPolicy:把任务交给添加此任务的线程来执行;

  3. DiscardPolicy:忽略此任务(最新加入的任务);

  4. DiscardOldestPolicy:忽略最先加入队列的任务(最老的任务)。

  5. 线程池的执行流程图:

    img

    使用ThreadPoolExecutor创建一个忽略最新任务的线程池

    创建规则:

    1.核心线程为5

    2.最大线程数为12

    3任务队列为100

    4拒绝策略为忽略最新任务

image-20221002104016720

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

public class HomeWork {
    public static void main(String[] args) {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,10,3,
                TimeUnit.SECONDS,new PriorityBlockingQueue<>(100),new ThreadPoolExecutor.DiscardOldestPolicy());
        //测试一下
        for(int i = 0;i< 200;i++){
            new Thread(() ->{
                System.out.println(Thread.currentThread().getName());
            },"thread "+(i+1)).start();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值