关于线程池,那些你还不知道的事

一、背景

  最近在学习线程相关的知识,然后顺理成章少不了学习线程池,刚开始在没有深入的学习之前,感觉线程池是很神秘的东西,而且完全想不到怎么才能实现一个自己的线程池,然后还能保证它的可用性,然后就一直琢磨,琢磨了一周才不多,也是网上各种查资料,终于明白了线程池的原理,也自己手写一个线程池,来加深印象,那么本文我们就来聊一聊关于线程池的知识,希望更多的猿友能看到,从此对线程池有一个清晰直观的认识。

二、概念解析

1.什么是线程池

  线程池的基本思想是一种对象池,在程序启动时就开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

2.使用线程池的好处

  合理的使用线程池可以重复利用已创建的线程,这样就可以减少在创建线程和销毁线程上花费的时间和资源。并且,线程池在某些情况下还能动态的调整工作线程的数量,以平衡资源消耗和工作效率。同时线程池还提供了对池中工作线程进行统一的管理的相关方法。这样就相当于我们一次创建,就可以多次使用,大量的节省了系统频繁的创建和销毁线程所需要的资源。

3.线程池的主要组件

一个线程池包括以下四个基本组成部分:
(1)、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
(2)、工作线程(WorkThread):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
(3)、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
(4)、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

4.JDK中线程池常用类UML类关系图

三、手写实现

我们知道了线程池的原理以及主要组件之后,就让我们来手动实现一个自己的线程池,以加深理解和深入学习。

1.线程池接口类

package com.hafiz.proxy.threadPool;

import java.util.List;

/**
 * Desc:线程池接口类
 * Created by hafiz.zhang on 2017/9/19.
 */
public interface ThreadPool {

    // 执行单个线程任务
    void execute(Runnable task);

    // 执行多个任务
    void execute(Runnable[] tasks);

    // 执行多个任务
    void execute(List<Runnable> tasks);

    // 返回已经执行的任务个数
    int getExecuteTaskNumber();

    // 返回等待被处理的任务个数,队列的长度
    int getWaitTaskNumber();

    // 返回正在工作的线程的个数
    int getWorkThreadNumber();

    // 关闭线程池
    void destroy();
}

2.线程池实现类ThreadPoolManager.java

package com.hafiz.proxy.threadPool;

import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Desc:线程池实现类
 * Created by hafiz.zhang on 2017/9/19.
 */
public class ThreadPoolManager implements ThreadPool {

    // 线程池中默认线程的个数为5
    private static Integer workerNum = 5;

    // 工作线程数组
    WorkThread[] workThreads;

    // 正在执行的线程任务数量
    private static volatile Integer executeTaskNumber = 0;

    // 任务队列, 作为一个缓冲
    private Queue<Runnable> taskQueue = new ConcurrentLinkedQueue<>();

    // 单例模式
    private static ThreadPoolManager threadPool;

    private AtomicLong threadNum = new AtomicLong();

    private ThreadPoolManager() {
        this(ThreadPoolManager.workerNum);
    }

    private ThreadPoolManager(int workerNum) {
        if (workerNum > 0) {
            ThreadPoolManager.workerNum = workerNum;
        }
        workThreads = new WorkThread[ThreadPoolManager.workerNum];
        for (int i = 0; i < ThreadPoolManager.workerNum; i++) {
            workThreads[i] = new WorkThread();
            Thread thread = new Thread(workThreads[i], "ThreadPool-worker-" + threadNum.incrementAndGet());
            thread.start();
            System.out.println("初始化线程总数:" + (i+1) + ",当前线程名称是:ThreadPool-worker-" + threadNum);
        }
    }

    public static ThreadPool getThreadPool() {
        return getThreadPool(workerNum);
    }

    public static ThreadPool getThreadPool(int workerNum) {
        if (workerNum > 0) {
            ThreadPoolManager.workerNum = workerNum;
        }
        if (threadPool == null) {
            threadPool = new ThreadPoolManager(ThreadPoolManager.workerNum);
        }
        return threadPool;
    }

    @Override
    public void execute(Runnable task) {
        synchronized (taskQueue) {
            taskQueue.add(task);
            taskQueue.notifyAll();
        }
    }

    @Override
    public void execute(Runnable[] tasks) {
        execute(Arrays.asList(tasks));
    }

    @Override
    public void execute(List<Runnable> tasks) {
        synchronized (taskQueue) {
            for (Runnable task : tasks) {
                 taskQueue.add(task);
            }
            taskQueue.notifyAll();
        }
    }

    @Override
    public String toString() {
        return "ThreadPoolManager{" +
                "当前的工作线程数量=" + getWorkThreadNumber() +
                ", 已完成的任务数=" + getExecuteTaskNumber() +
                ", 等待任务数=" + getWaitTaskNumber() +
                ‘}‘;
    }

    @Override
    public int getExecuteTaskNumber() {
        return executeTaskNumber;
    }

    @Override
    public int getWaitTaskNumber() {
        return taskQueue.size();
    }

    @Override
    public int getWorkThreadNumber() {
        return workerNum;
    }

    @Override
    public void destroy() {
        while (!taskQueue.isEmpty()) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int i = 0; i < workThreads.length; i++) {
            workThreads[i].shutdown();
            workThreads[i] = null;
        }
        threadPool = null;
        taskQueue.clear();
    }

    private class WorkThread implements Runnable {
        // 线程是否可用
        private boolean isRunning = true;

        @Override
        public void run() {
            Runnable r = null;
            while (isRunning) {
                // 队列同步机制,加锁
                synchronized (taskQueue) {
                    while (isRunning && taskQueue.isEmpty()) {
                        try {
                            taskQueue.wait(20);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if (!taskQueue.isEmpty()) {
                        r = taskQueue.poll();
                    }
                }
                if (r != null) {
                    r.run();
                }
                executeTaskNumber++ ;
                r = null;
            }
        }

        public void shutdown() {
            isRunning = false;
        }
    }
}

其中该类中包含内部类WorkThread,它用来包装真正的线程类,给每一个线程一个是否可用的标志,该线程工作室同步的从taskQueue中取出要执行的任务进行调用run方法来执行任务。

这个类中的getThreadPool方法中我们还使用到了懒汉式来实现单例,单例模式也是Java常用设计模式之一。

注意该类中的destroy方法的实现:我们是一直等到队列中的所有的任务执行完毕,才真正的销毁线程池,销毁的过程中不要忘记将每一个线程对象置为null,并且清空任务队列,这样更利于java的垃圾回收。

3.自定义任务类Task.java

package com.hafiz.proxy.threadPool;

/**
 * Desc:自定义任务类
 * Created by hafiz.zhang on 2017/9/21.
 */
public class Task implements Runnable {

    private static volatile Integer i = 1;

    @Override
    public void run() {
        // 执行任务
        synchronized (i) {
            System.out.println("当前处理的线程是:" + Thread.currentThread().getName() + ",执行任务:" + (i++) + "完成");
        }
    }
}

4.线程池测试类

package com.hafiz.proxy.threadPool;

import java.util.ArrayList;
import java.util.List;

/**
 * Desc:线程池测试类
 * Created by hafiz.zhang on 2017/9/20.
 */
public class ThreadPoolTest {
    public static void main(String[] args) {
        ThreadPool t = ThreadPoolManager.getThreadPool(6);
        List<Runnable> tasks = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            tasks.add(new Task());
        }
        System.out.println(t);
        t.execute(tasks);
        // 所有的线程执行完成才destroy
        t.destroy();
        System.out.println(t);
    }
}

5.测试结果:(为了篇幅,只创建10个任务运行)

四、总结

  通过本文,我们弄明白线程池到底是怎么工作,学习知识的过程中,我们就是要知其然知其所以然。这样我们才能更好地驾驭它,才能更好地去理解和使用,也能更好地帮助我们触类旁通,后面有机会我们接着来说数据库连接池的原理及手写实现。

时间: 2024-11-10 01:16:08

关于线程池,那些你还不知道的事的相关文章

你可能还不知道的ES6的事儿

ES6,或许应该叫 ES2015(2015 年 6 月正式发布),对于大多数前端同学都不陌生. 首先这篇文章不是工具书,不会去过多谈概念,而是想聊聊关于每个特性 你可能不知道的事,希望能为各位同学正确学习 ES6 ,提供一些指导. 对于 ES6,有些同学已经在项目中有过深入使用了,有些则刚刚开始认识他,但不论你是属于哪一类,相信这篇文章都有适合你的部分.针对文章中的问题或不同意见,欢迎随时拍砖.指正. 正文 Let + Const 这个大概是开始了解 ES6 后,我们第一个感觉自己完全明白并兴致

CSS你可能还不知道的一些知识点--转

作者:软谋教育出处:http://www.cnblogs.com/ruanmou CSS你可能还不知道的一些知识点 一.特殊选择器 1.* 用于匹配任何的标记 2.> 用于指定父子节点关系 3.E + F 毗邻元素选择器,匹配所有紧随E元素之后的同级元素F 4.E ~ F 匹配所有E元素之后的同级元素F 5.名称[表达式] 5.1 E[att] 匹配所有具有att属性的E元素([att]获取所有的定义了att的标签:E[att=val] 匹配所有att属性等于“val”的E元素: 5.2 [at

关于线性模型你可能还不知道的二三事(二、也谈民主)

目录 1 如何更新权值向量?2 最小均方法(LMS)与感知机:低效的民主3 最小二乘法:完美的民主4 支持向量机:现实的民主5 总结6 参考资料 1 如何更新权值向量? 在关于线性模型你可能还不知道的二三事(一.样本)中我已提到如何由线性模型产生样本,在此前提下,使用不同机器学习算法来解决回归问题的本质都是求解该线性模型的权值向量W.同时,我们常使用线性的方式来解决分类问题:求解分隔不同类别个体的超平面的法向量W.不论回归还是分类,都是求解向量W,而求解的核心思想也英雄所见略同:向量W倾向于指向

网站细节优化,你不得不知道的事

你怎么做优化的?这句话问到许多人的时分他们都是一个回答."文章,外链",其实,搜索引擎优化真的并不是如此.下面天津搜索引擎优化小编举个简略的比方,就好比咱们买了个新房子,文章即是你房子中的家私,外链即是你需求来观赏的兄弟.你的家私多,而且异乎寻常,也即是说你的文章多,自创,当然可以很招引你需求到来的兄弟们的眼球.可是,如果你的房子地没扫,各种规划不合理,很有可能会给别人形成肮脏的印象.所以,做搜索引擎优化并不是只要文章还有外链,还需要你进行仔细的润饰,也即是咱们做的网站细节优化.下面我

ES6 你可能不知道的事 – 基础篇

ES6 你可能不知道的事 – 基础篇 转载 作者:淘宝前端团队(FED)- 化辰 链接:taobaofed.org/blog/2016/07/22/es6-basics/ 序 ES6,或许应该叫 ES2015(2015 年 6 月正式发布),对于大多数前端同学都不陌生. 首先这篇文章不是工具书,不会去过多谈概念,而是想聊聊关于每个特性 你可能不知道的事,希望能为各位同学 正确使用 ES6,提供一些指导. 对于 ES6,有些同学已经在项目中有过深入使用了,有些则刚刚开始认识他,但不论你是属于哪一类

overflow:hidden 你所不知道的事

overflow:hidden 你所不知道的事 overflow:hidden这个CSS样式是大家常用到的CSS样式,但是大多数人对这个样式的理解仅仅局限于隐藏溢出,而对于清除浮动这个含义不是很了解. 一提到清除浮动,我们就会想到另外一个CSS样式:clear:both,我相信对于这个属性的理解大家都不成问题的.但是对于“浮动”这个词到底包含什么样的含义呢?我们下面来详细的阐述一下. <div id="wai"> <div id="nei">

Android生命周期里你或许不知道的事

Android生命周期估计连初学者都再熟悉不过的东西了,但这里我抛出几个问题,或许大家以前没有想过或者可能认识的有些错误. 一.当A启动B时,A和B生命周期方法执行的先后顺序是怎样的?当按返回键返回时,又是怎样的?(读者可以先想想,可能会跟你的答案不一致) A--->B时,打印结果如下: 按返回键B--->A,打印结果如下: 结论:先执行当前显示Activity的onPause方法,接着执行完将要显示Activity的生命周期方法,最后再执行当前显示Activity的其它生命周期方法 二.生命

线程池 Threadpool (还需要补充)

1.池(pool)是一种常见的提高性能的方式.有时创建和销毁一些资源是非常耗费时间的行为.我们会考虑在一个池内放一些资源,在用的时候拿,不够的时候添加,用完就归还,这样就可以避免不断的创建和销毁资源了. 应用场合和范围 需要大量的线程来完成任务,切完成任务的时间比较短. 对性能要求比较苛刻的应用.比如要求服务器迅速响应客户请求. 3.接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用.突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系 统线程数目最大值不是问

jdk1.8新特性,还不知道的朋友还不看看,1.9都快出来了

一.接口的默认方法 Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:代码如下:interface Formula {    double calculate(int a); default double sqrt(int a) {        return Math.sqrt(a);    }} Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个ca