最近学习了一下极客时间的《Java 并发编程实战》对Java的线程池有了新的认识并且有了自己的理解,以及一些设想,例如能否设计一个分布式的线程池,能否设计一个可以动态修改参数的线程池,能否设计一个可以实时监控任务执行情况的线程池,因此发表这篇帖子,希望与大家一起进步,一起天马星空。
首先:我想说一下自己接触编程5年对线程池的理解心路历程,初学代码一年搞懂C语言的计算器(Computer),入行两年接触面向对象的编程思想,接触很多书籍,很多博客,大多数声音在说,面向对应的语言Java是比较有代表性的,所谓面向对象编程就是你要面对着你对象进行编写代码,哈哈,这很显然是一句玩笑话,其实就是代码不是一行一行的顺序编写,而是需要类比搭建积木一样具有模块行,把对应的功能模块,功能职责,定义给特定的功能类(Class),首先我们来了解面向对象的好处,所有的理论,以及新的技术都是先有需求后有方案,他与学习不同,学习很多时候是先学习公式,然后根据公式进行类型题目的解决,反复练习,选择题,应用题,所以很多受过教育的同学,都会有一个学习技术的误区,先了解概念公式然后来了解应用场景,这好像是买了一个电动螺丝刀然后试着把家里能拧🤏的东西都拧了一次,其实并不需要被修理,所以我们应该先忘记解决方案,先去发现问题,先去想解决问题的方法论,然后去看有哪些落地的解决方案,然后根据已经落地的解决方案,升级定制自己的DIY解决方案(Worldly Wisdom teaches that it is better for reputation to fail conventionally than to succeed unconventionally.),那我们顺着这个思路来看待这个问题来思考🤔,面向过程编写代码的风格会在代码量很大的时候无法维护,很难扩展,例如我想为一个计算器的加减乘除统一把结果取整,很难吧?因此Java这类面向对象(OOP)的编程语言就可以根据它的语言特性 封装,继承,多态,来衍生出很多设计原则,设计模式,例如Open Close原则,单一职责,这些编程思想,是需要到达某一个代码量的积累才会有的一种感觉,无法言述。
我们言归正传,Java的多线程,大家都应该听说过,使用的应该很少,因为系统的大部分开发工作都是在Spring框架生态下进行的web应用开发,主流的服务器Tomcat就是多线程的我们只需要在SpringMvc的体系下面写Service只要注意Spring的Bean在默认情况下是单例的(也就是Sprint管理的Bean的对象默认是全局唯一的,当然可以修改级别)也就是不再service里面定义类级别的常量就可以,大多数程序员不需要考虑多线程开发,但是也有少数的应用场景,例如一些数据的批量处理任务,就会创建一个线程,摆脱主线程的同步等待束缚,跳过处理,进行其他的工作,那么系统当中已经使用spring的容器来管理对象 ,减少了创建对象进行的资源浪费,然而创建一个线程可不是一个简单的对象那么简单会很浪费资源具体的逻辑会在下一篇文章中详解。
创建一个线程的两种方案:
public class TestThread extends Thread {
@Override
public void run() {
System.out.println("test");
}
public static void main(String[] args) {
TestThread testThread = new TestThread();
testThread.start();
}
}
public class TestThread implements Runnable {
@Override
public void run() {
System.out.println("test");
}
public static void main(String[] args) {
TestThread testThread = new TestThread();
Thread thread = new Thread(testThread);
thread.start();
}
}
当系统中有大量的线程任务被创建时就会出现系统资源被耗尽的情况,那么如何解决这个事情,首先考虑的思路有两点:
第一点:统一管理线程
第二点:控制线程执行
对于解决这两个问题衍生出我们需要一个线程的容器进行存储线程任务,并且需要按照设定的规则来进行执行,我们会想到池化技术,就是把线程创建好放在一个线程池里面,然后线程池按照设定的规则进行执行,这个时候你就会去看线程池的源码,然后发现并没有对应的申请资源的方法,其实线程池的技术是消费者模型具体我写了一个demo的代码:
public class MyThreadPool {
//堵塞队列用来进行存储需要被执行的线程任务
BlockingQueue<Runnable> workQueue;
List<WorkerThread> threads = new ArrayList<>();
ThreadFactory threadFactory;
MyThreadPool(int poolSize, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
this.workQueue = workQueue;
this.threadFactory = threadFactory;
for (int i = 0; i < poolSize; i++) {
WorkerThread workerThread = new WorkerThread();
workerThread.start();
threads.add(workerThread);
}
}
void execute(Runnable task){
try {
workQueue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class WorkerThread extends Thread {
@Override
public void run() {
while (true) {
Runnable task = null;
try {
task = workQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
task.run();
}
}
}
}
public class MyThreadPoolApplication {
public static void main(String[] args) {
MyThreadPool myThreadPool = new MyThreadPool(5, new ArrayBlockingQueue<Runnable>(2, false), r -> new Thread(r, "线程:" + Thread.currentThread().getId()));
for (int i = 0; i < 50; i++) {
myThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + ":你好");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
代码中有一个堵塞队列用(workQueue)来存储提交的线程任务其实就是一个传递了一个实现了run方法的一个特定类,你可以把它想象成存储的是一个功能函数,一般我们建议使用有界限的堵塞队列,可以定义一个handle的功能函数的实现类,规定在有界队列满的时候如何执行。
代码中还有一个工作队列(threads)是线程池里面真正工作的线程,根据定义的线程池大小来初始化工作线程数量,并且启动单一工作线程不断堵塞执行任务,当然了ThreadPoolThread会比这个复杂很多,但是原理都是一样的,是一个消费者模型。
执行线程任务的防范是execute,只是用来为堵塞队列添加任务。
借鉴:极客时间《Java 并发编程实战》