Java并发——线程池原理
“池”技术对我们来说是非常熟悉的一个概念,它的引入是为了在某些场景下提高系统某些关键节点性能,最典型的例子就是数据库连接池,JDBC是一种服务供应接口(SPI),具体的数据库连接实现类由不同厂商实现,数据库连接的建立和销毁都是很耗时耗资源的操作,为了查询数据库中某条记录,最原始的一个过程是建立连接、发送查询语句、返回查询结果、销毁连接,假如仅仅是一个很简单的查询语句,那么可能建立连接与销毁连接两个步骤就已经占所有资源时间消耗的绝大部分,如此低下的效率显然让人无法接受。针对这个过程是否能通过某些手段提高效率,于是想到的尽可能减少创建和销毁连接操作,因为连接相对于查询是无状态的,不必每次查询都重新生成销毁,我们可以把这些通道维护起来供下一次查询使用,维护这些管道的工作就交给了“池”。
线程池也是类似于数据库连接池的一种池,而仅仅是把池里的对象换成了线程。线程是为多任务而引入的概念,每个线程在任意时刻执行一个任务,假如多个任务要并发执行则要用到多线程技术。每个线程都有自己的生命周期,以创建为始销毁为末。如下图,两个线程运行阶段占整个生命周期的比重不同,运行阶段所占比重小的线程可以认为其运行效率低,反观下面一条线程则认为运行效率高。在大多数场景下都比较符合图上面的线程运行模式,例如我们常见的web服务、数据库服务等等。为了提高运行效率引入线程池,它的核心思想就是把运行阶段尽量拉长,对于每个任务的到来不是重复建立销毁线程,而是重复利用之前建立好的线程执行任务。
其中一种方案是在系统启动事建立好一定数量的线程并做好线程维护工作,一旦有任务到来即从线程池中取出一条空闲的线程执行任务。原理听起来比较清晰,但现实中对于一条线程,一旦调用start方法后就将运行任务直到任务完成,随后JVM将对线程对象进行GC回收,如此一来线程不就销毁了吗?是的,所以需要换种思维角度,让这些线程启动后通过一个无限循环来执行指定的任务,下面将重点讲解如何实现线程池。
一个线程池的属性起码包含初始化线程数量、线程数组、任务队列。初始化线程数量指线程池初始化的线程数,线程数组保存了线程池中所有线程,任务队列指添加到线程池等待处理的所有任务。如下图,线程池里有两条线程,池里线程的工作就是不断循环检测任务队列中是否有需要执行的任务,如果有则处理并移出任务队列。于是可以说线程池中的所有线程的任务就是不断检测任务队列并不断执行队列中的任务。
看一个最简单粗糙的线程池的实现,使用线程池是只需实例化一个对象,构造函数会创建相应数量的线程并启动线程,启动的线程无限循环检测任务队列,执行方法execute()仅仅把任务添加到任务队列中。有一点需要注意的是所有任务都必须实现Runnable接口,这是线程池的任务队列与工作线程的约定,juc工具包作者Doug Lea大神当时如此规定,工作线程检测任务队列并调用队列的run()方法,假如你自己重新写一个线程池是完全可以自己定义一个不一样的任务接口。一个完善的线程池并不像下面例子简单,需要提供启动、销毁、增加工作线程的策略、最大工作线程数、各种状态的获取等等操作,而且工作线程也不可能老是做无用循环,需要对任务队列使用wait、notify优化或任务队列改用阻塞队列。
public final class ThreadPool {
private final int worker_num;
private WorkerThread[] workerThrads;
private List<Runnable> taskQueue = new LinkedList<Runnable>();
private static ThreadPool threadPool;
public ThreadPool(int worker_num) {
this.worker_num = worker_num;
workerThrads = new WorkerThread[worker_num];
for (int i = 0; i < worker_num; i++) {
workerThrads[i] = new WorkerThread();
workerThrads[i].start();
}
}
public void execute(Runnable task) {
synchronized (taskQueue) {
taskQueue.add(task);
}
}
private class WorkerThread extends Thread {
public void run() {
Runnable r = null;
while (true) {
synchronized (taskQueue) {
if (!taskQueue.isEmpty()) {
r = taskQueue.remove(0);
r.run();
}
}
}
}
}
}
通过上面已经清楚了线程池原理,但并不提倡重造轮子行为,因为线程池处理不好很容易产生死锁问题,同时线程池内状态同步操作不当也可能导致意想不到的问题,除此之外还有很多其他的并发问题,除非是很有经验的并发程序员才能尽可能减少可能的错误。我们直接使用jdk的juc工具包即可,它由Doug Lea编写的优秀并发程序工具,单线程池就已经提供了好多种类的线程池,实际开发中根据需求选择合适的线程池。
喜欢研究java的同学可以交个朋友,下面是本人的微信号:
Java并发——线程池原理的更多相关文章
- 【java】-- 线程池原理分析
1.为什么要学习使用多线程? 多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担. 线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致 ...
- Java并发--线程池的使用
在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统 ...
- Java并发线程池到底设置多大?
前言 在我们日常业务开发过程中,或多或少都会用到并发的功能.那么在用到并发功能的过程中,就肯定会碰到下面这个问题 并发线程池到底设置多大呢? 通常有点年纪的程序员或许都听说这样一个说法 (其中 N 代 ...
- <关于并发框架>Java原生线程池原理及Guava与之的补充
原创博客,转载请联系博主! 转眼快两个月没有更新自己的博客了. 一来感觉自己要学的东西还是太多,与其花几个小时写下经验分享倒不如多看几点技术书. 二来放眼网上已经有很多成熟的中文文章介绍这些用法,自己 ...
- Java并发——线程池Executor框架
线程池 无限制的创建线程 若采用"为每个任务分配一个线程"的方式会存在一些缺陷,尤其是当需要创建大量线程时: 线程生命周期的开销非常高 资源消耗 稳定性 引入线程池 任务是一组逻辑 ...
- Java ThreadPoolExecutor线程池原理及源码分析
一.源码分析(基于JDK1.6) ThreadExecutorPool是使用最多的线程池组件,了解它的原始资料最好是从从设计者(Doug Lea)的口中知道它的来龙去脉.在Jdk1.6中,Thread ...
- java并发线程池---了解ThreadPoolExecutor就够了
总结:线程池的特点是,在线程的数量=corePoolSize后,仅任务队列满了之后,才会从任务队列中取出一个任务,然后构造一个新的线程,循环往复直到线程数量达到maximumPoolSize执行拒绝策 ...
- Java并发—线程池框架Executor总结(转载)
为什么引入Executor线程池框架 new Thread()的缺点 每次new Thread()耗费性能 调用new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制创建,之间相互竞 ...
- Java并发-线程池篇-附场景分析
作者:汤圆 个人博客:javalover.cc 前言 前面我们在创建线程时,都是直接new Thread(): 这样短期来看是没有问题的,但是一旦业务量增长,线程数过多,就有可能导致内存异常OOM,C ...
随机推荐
- NOIP2016 玩脱记
NOIP前: NOIP前停课了一个多月,这一个多月里浪得飞起,内心十分紧张,然后就不知不觉就到NOIP了. Day 0: 上火车前ryc给我们出了道题"一个数列,只有两个数出现了奇数次,找出 ...
- java版的类似飞秋的局域网在线聊天项目
原文链接:http://www.cnblogs.com/wangleiblog/articles/5323305.html 转载请注明 最近在弄一个java版的局域网在线聊天项目,功能跟飞秋差不多.p ...
- Python的IO编程
原文传送门:请点击 原文传送门:请点击
- c++ 变量的存储类别
c++的存储类别 首先我们得知道c++的变量存储方式:静态存储和动态存储两种,全局变量使用的是静态存储,函数的形参和局部变量是使用的动态存储. 当然在有的教程中又分为自动存储,静态存储,动态存储.相信 ...
- jsp根据参数默认选中radio
<% int vol = (Integer)request.getAttribute("cardtype") ; %> <input type="rad ...
- 11.QT-布局管理器(Box,Grid,Form,Stacked)
布局管理器简介 QT中提供了对界面组件进行布局管理的类,用于对界面组件进行管理, 能够自动排列窗口中的界面组件 窗口大小变化后,便会自动更新界面组件的大小. 布局管理器可以自定义,从而达到更加个性化界 ...
- struts2中的使用BaseAction获取Session
package com.owen.ma; import java.util.Map; import org.apache.struts2.interceptor.RequestAware; impor ...
- 去除html标记和替换script标记
1: /// <summary> 2: /// 去除HTML标记 3: /// </summary> 4: /// <param name="NoHTML&qu ...
- c++ 文件操作详解
C++ 通过以下几个类支持文件的输入输出: ofstream: 写操作(输出)的文件类 (由ostream引申而来) ifstream: 读操作(输入)的文件类(由istream引申而来) fstre ...
- Oracle中的4大空值处理函数用法举例
nvl(exp1,exp2): 如果exp1为空,则返回exp2:否则返回exp1nvl2(exp1,exp2,exp3): ...