实现一个简易的线程池

  • 一、行文介绍
  • 二、线程池的概念
  • 三、线程池的原理
  • 四、阻塞队列介绍
  • 五、概念科普
  • 六、简单的线程池模型实现
  • 七、分析总结


一、行文介绍

实际上本人没有实战地使用过线程池,写这篇文章仅仅是出于对线程复用的兴趣。
本文将通过在网上搜集到的资料以及自己编写的简易版的线程池来说明线程复用技术。

二、线程池的概念

线程池是一种提高系统性能的技术,可以创建一定数量的工作线程,当向线程池提交任务时,线程池中的空闲线程将会接收任务并执行,执行完之后线程并不会马上关闭,而是处于空闲状态,等待下一个任务的到达。
在这里执行任务避免了大量线程的创建与销毁工作,从而提高了系统效率。

三、线程池的原理

线程池的关键技术是 线程复用 ,如何实现线程复用呢?
我们可以这样理解,现在线程池中创建了数量一定的线程,并start(),每个线程进入一个循环体,循环体内线程请求一个 阻塞队列 获取一个Runnable对象,然后并不是像启动线程那样启动新的线程,而是直接调用该对象的run()方法。
这样,只要阻塞队列里有元素,那么线程池中的线程就能够拿到一个元素,然后调用该对象的run()方法,每一次执行就相当于开启了一个新的线程,而实际上仅仅是 复用 了线程池里的线程。

四、阻塞队列介绍

这里简单介绍一下什么是阻塞队列,有强烈学习欲望的同学建议网上搜类似内容。
阻塞队列,它本质上是一个队列,遵循着FIFO原则,但它实现了BlockingQueue接口,多了两个方法:

public interface BlockingQueue<E> extends Queue<E> {
    void put(E var1) throws InterruptedException;

    E take() throws InterruptedException;
}

这里仅仅截取需要了解的源码。
put()take(),前者 阻塞地 入队,后者 阻塞地 出队。

五、概念科普

阻塞是什么意思?
简单地说,就是当线程遇到锁或者其他原因导致的线程无法继续运行了,就称为阻塞,等到某一通知,线程才会从阻塞队列移除。

六、简单的线程池模型实现

先定义线程池的接口:

public interface ThreadPool {
	//提交任务
    void execute(Runnable target);
	//返回线程池线程的数量
    int getPoolSize();
}

创建线程池的工厂模式:

public class ThreadPoolFactor{
	//返回一个线程池
    public static MyThreadPool getMyThreadPool(){
        return new MyThreadPool(20,new BlockingArrayQueue<>(10));
    }
}

简易版的线程池类:

public class MyThreadPool implements ThreadPool {

    //当前线程池里的线程
    private ArrayList<Thread> workers;
    //当前线程池的线程数量,实际上在这里这个变量没意义
    private volatile int poolSize;
    //当前线程池的最大数量
    private volatile int maxPoolSize;
    //线程池数量的默认值,实际上没有定义无参构造函数,在这里仅仅作为下界使用
    private static final int DEFAULT_CAPACITY = 10;
    //阻塞式的任务队列
    private BlockingQueue<Runnable> workQueue;
    //可重入锁,实际上在这里也是没有意义
    private static final ReentrantLock lock = new ReentrantLock();

    //避免麻烦,全部都先定义好
    public MyThreadPool(int maxPoolSize, BlockingQueue<Runnable> workQueue) {
        if (maxPoolSize < DEFAULT_CAPACITY) {
            maxPoolSize = DEFAULT_CAPACITY;
        }
        this.workers = new ArrayList<Thread>(maxPoolSize);
        this.maxPoolSize = maxPoolSize;
        this.workQueue = workQueue;
        for (int i = 0; i < maxPoolSize; i++) {
            Thread worker = new Thread(new Worker(workQueue));
            workers.add(worker);
            worker.start();
        }
    }

    @Override
    public void execute(Runnable target) {
    	//把任务提交到任务队列,这里没有使用put()是因为会报错,原因暂不明
        workQueue.offer(target);
    }

    @Override
    public int getPoolSize() {
        return poolSize;
    }

    private static final class Worker implements Runnable {
        Runnable target;
        BlockingQueue workQueue;

        public Worker(BlockingQueue workQueue) {
            this.workQueue = workQueue;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "---->" + "start()");
            //此线程将不会停止,当然这么粗暴的方式是因为没有考虑其他情况
            while (true) {
                try {
                	//阻塞式地取任务,当队列为空时,当前线程将阻塞等待
                    target = (Runnable) workQueue.take();
                    //执行任务
                    target.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

编写测试类:

public class ThreadPoolTest {
    public static void main(String[] args) throws InterruptedException {
    	//创建线程池
        ThreadPool threadPool = ThreadPoolFactor.getMyThreadPool();
        //每个一秒,往线程池里扔1个任务,一共1000个
        for (int i=0;i<1000;i++){
            int finalI = i;
            Thread.sleep(1000);
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                	//任务就是打印当前线程名和此时执行的任务的ID
                    System.out.println(Thread.currentThread().getName()+"---->now-->"+ finalI);
                }
            });
        }
    }
}

输出结果:
1.创建了20个线程并且全部都运行了

Thread-17---->start()
Thread-19---->start()
Thread-10---->start()
Thread-18---->start()
Thread-0---->start()
Thread-3---->start()
Thread-12---->start()
Thread-5---->start()
Thread-11---->start()
Thread-7---->start()
Thread-13---->start()
Thread-1---->start()
Thread-14---->start()
Thread-4---->start()
Thread-8---->start()
Thread-16---->start()
Thread-6---->start()
Thread-9---->start()
Thread-2---->start()
Thread-15---->start()

2.执行任务的部分截取

Thread-10---->now-->0
Thread-3---->now-->1
Thread-19---->now-->2
Thread-18---->now-->3
Thread-0---->now-->4
Thread-12---->now-->5
Thread-5---->now-->6
Thread-17---->now-->7
Thread-11---->now-->8
Thread-7---->now-->9
Thread-13---->now-->10
Thread-1---->now-->11
Thread-14---->now-->12
Thread-4---->now-->13
Thread-8---->now-->14
Thread-16---->now-->15
Thread-6---->now-->16
Thread-9---->now-->17
Thread-2---->now-->18
Thread-15---->now-->19
Thread-10---->now-->20
Thread-3---->now-->21
Thread-19---->now-->22
Thread-5---->now-->46
Thread-17---->now-->47

七、分析总结

从程序执行结果来看:

Thread-5---->now-->46
Thread-5---->now-->6

可以看出,同样的线程执行了不同的任务,可以说明,此程序已经基本上实现了线程的复用了。
之前接触过数据库连接池,使用的是代理模式,这一次想试着使用代理+反射来实现线程池,发现虽然能够实现相同的功能,但是却会报错,经过几天的查找资料,最终是使用了这一种实现方式。

参考.