深入线程池原理和自定义线程池

        自JDK1.5起,untils包提供了ExecutorService线程池的实现,主要目的就是为了重复利用线程,提高系统效率,我们知道线程的创建、启动及销毁都是比较占资源的,因此对线程的重复利用是一种非常好的程序设计习惯,加之系统创建线程的数量是有限的,线程的数量和系统的性能是一种抛物线关系,也就是说达到一定数量后,性能会降低很多。

那我们就思考线程是不是越多越好呢?

        1.因为创建线程和销毁线程都是需要时间和资源的,假设你任务执行时间很短,甚至比(创建+销毁)线程的时间还短,那是不是不值得。

        2.java对象占用堆内存,操作系统线程占用系统内存,根据jvn规范一个线程最大默认的栈大小为1M, 这个栈内存是需要从系统内存中分配的,线程过多占用内存。

        3.我们可以把每个线程想象成是一个商场上买东西的顾客,现在它们要一同前往收银台付款。假设我们商场上有四个收银柜台,这时候一拥而上了好几百顾客,那这时候四个收银小姐姐肯定是不足以来应付这么多消费者的。比如收银员小芳刚想和a结账,结果这时候b又来了,那么a的结账的任务只能先放着,小芳又转头去执行b的任务,那么这就会导致我们说的线程上下文切换,也就是cpu频繁的切换线程上下文。显然这也是不利于我们效率的提升,那肯定是排着队大家尽然有序的执行视为上策。

怎么让线程乖乖听话排队执行

        其实线程的本质就是一个搬运工,将代码运送到cpu让它去执行,那我们如何去管理这些线程呢?这时候就要用到线程池,说白了就是丢任务到这个池子里面,然后这个池子里规定有限的线程,这些线程就会帮你把任务运送到cpu让其执行,但是如果我们任务很多呢?怎么做到让其他线程乖乖听话在池子里面等待呢?只有等任务完成,线程卡车归位在继续往cpu里运输。

        这时候就需要用到阻塞队列了(队列为空时拉取阻塞,队列满时放入阻塞),具体用法请参考我的另一篇博文:URL,对于不能立即满足但可以在某一个时刻满足的操作。会有四种形式出现:1、返回异常  2、返回一个特殊指值(null/或者false,具体看你的操作)  3、操作可以在成功前,无限期的阻塞该线程  4、操作可以在成功前,指定阻塞时间来阻塞该线程

线程池原理:

    所谓线程池通俗的理解就是有一个池子,里面存放着已经创建好的线程,当有任务提交给池子执行时,池子某个线程委以重任,如果任务较多,线程不足应付众多任务时,则需要自动扩充新的线程到池子。就好比池塘的水界线一样,任务较少,池子线程自动回收释放资源,为了能够异步提交任务和缓存违背未被处理的任务,需要有一个任务队列,具备以下要素。

1、任务队列:用于缓存提交的任务

2、线程数量管理功能:一个线程池必须能够很好的管理和控制线程的数量,可通过如下三个参数实现,比如创建线程出事的线程数量init、线程池自动扩充时最大的线程数量max、在线程空闲时需要释放的线程但也要维护一定数量的活跃线程或者核心数量core,有了这三个参数就能控制池中的数量三者关系为:init<=core<=max

3、任务拒绝策略:如果线程的数量已经达到上限且队列已满,则需要相应的拒绝策略来通知任务者。

4、线程工厂:主要用于个性化定制线程,比如将线程设为守护线程和设置线程名称等。

5、QueueSize:任务队列主要存放提交的Runnable,但是为了防止内存溢出,需要有limit数量对其控制。

6、keepedalive时间:该时间主要决定线程各个重要参数自动维护的时间间隔。

如何写自定义线程

        我们不用写这么复杂的线程池的实现,为了深入浅出的理解线程池,我们自定义写一个固定大小线程的线程池,首先我们明白到要自己创建一个线程必须要有任务仓库用来盛放runnable任务,其次你任务需要执行这里我们就需要一个线程工厂去创造这写线程,我们这里简化一下用线程集合来替代。好现在两个容器都有了,但是你需要具体的任务执行者也就是线程实例,所以这时候就需要一个工作者去继承Thread类,复写其中的run方法,run方法中就是从队列中取任务runnable,这个任务队列怎么来呢,则需要我们初始化Wroker来任务队列最为参数传给我们,然后执行runnable的run方法。

        OK,经过我们上面简单的分析说白了,就是在你初始化线程池的时候会启动你规定的线程数。当你来submit任务了,就往任务队列中放任务,如果超出你的任务队列则看你这边怎么解决,如果你想阻塞则调用阻塞队列的阻塞方法put()如果你想拒绝,则调用队列的offer或者调用add()抛出异常,就这么简单(当然我这里是简化版本的),所以一顿代码操作如下。

package com.cloud.ceres.rnp.Neek.threadpool;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * @author heian
 * @create 2020-02-03-2:20 下午
 * @description 自定义 有限线程数
 */
public class MyFiexThreadPool {

    //任务仓库
    private LinkedBlockingQueue runnables;

    //线程队列
    private List threads;

    private volatile boolean isRunning = true;

    //执行任务的打工仔  通过调用线程的start方法去处理runnable
    class Worker extends Thread{

        //打工仔跑任务必须是从同一个的线程池中取任务
        private MyFiexThreadPool pool;

        public Worker(MyFiexThreadPool pool){
            this.pool = pool;
        }

        @Override
        public void run() {
            Runnable runnable = null;
            while (this.pool.isRunning || this.pool.runnables.size()>0){
                try {
                    //执行了线程关闭,则不需要阻塞
                    if (this.pool.isRunning){
                        runnable = pool.runnables.take();
                    }else {
                        runnable = pool.runnables.poll();
                    }
                } catch (InterruptedException e) {
                    System.out.println("该线程被中断" + Thread.currentThread().getName());
                }
                if (runnable != null)
                    runnable.run();
                    System.out.println("当前线程:" + Thread.currentThread().getName() + "执行完毕");
            }
        }
    }

    /**
     * TODO 这里阻塞的是main线程不是我们线程池中的线程
     * this.runnables.put(runnable); //如果你想任务阻塞,不想失去任务则使用阻塞方法
     */
    public boolean submit(Runnable runnable) throws InterruptedException {
        boolean bool = this.runnables.offer(runnable);// 如果超出队列限制你想丢弃就用这个
        if (!bool)
            System.out.println(Thread.currentThread().getName() + "已经达到任务队列上限:"+this.runnables.size()+ ",任务被丢弃" );
        return false;
    }

    public MyFiexThreadPool(int initThreadSize,int queueSize){
        if (initThreadSize <=0 || queueSize<=0)
            throw new IllegalArgumentException("非法参数");
        //初始化两个容器
        this.threads = Collections.synchronizedList(new ArrayList<>());
        this.runnables = new LinkedBlockingQueue<>(queueSize);
        for (int i=0;i {
            if (thread.getState().equals(Thread.State.BLOCKED)){
                thread.interrupt();
            }
        });
    }

    public static void main(String[] args) throws InterruptedException {
        MyFiexThreadPool pool = new MyFiexThreadPool(2,5);
        for (int i=0;i<10;i++){
            pool.submit(() -> {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        pool.shutdown();

    }


}
main已经达到任务队列上限:4,任务被丢弃
main已经达到任务队列上限:5,任务被丢弃
main已经达到任务队列上限:5,任务被丢弃
当前线程:Thread-0执行完毕
当前线程:Thread-1执行完毕
当前线程:Thread-0执行完毕
当前线程:Thread-1执行完毕
当前线程:Thread-0执行完毕
当前线程:Thread-1执行完毕
当前线程:Thread-0执行完毕

这里需要注意的就是执行线程关闭方法  需要注意三点:

  1. 线程池关闭,停止继续向线程池投放任务
  2. 线程池关闭,假设池中还有任务必须得等到线程池中的任务执行完才执行关闭操作
  3. 线程池关闭,假设在此之前就已经存在阻塞的线程了则需要先把该线程打断

合理设置线程数和队列数

  1. 计算型任务,则线程数为cpu的1-2倍
  2. io型任务,则需要根据阻塞时常进行考量,如tomcat最大线程为200

 

 

 

 

 

 

你可能感兴趣的:(Java多线程)