Java高并发 -- 线程池
主要是学习慕课网实战视频《Java并发编程入门与高并发面试》的笔记
在使用线程池后,创建线程变成了从线程池里获得空闲线程,关闭线程变成了将线程归坏给线程池。
JDK有一套Executor框架,大概包括Executor、ExecutorService、AbstractExeccutorService、ThreadPoolExecutor、Executors等成员,位于java.util.concurrent
包下。它们之间的关系如下:
Executor是顶层的接口,ExecutorService接口继承了它,AbstrctExecutorService继承了ExecutorService,ThreadPoolExecutor继承了AbstrctExecutorService。如果用<——
表示继承,<--
表示实现接口,它们的关系可表示如下:
Executor(接口) <—— ExecutorService(接口) <-- AbstrctExecutorService(抽象类) <—— ThreadPoolExecutor(类)
Executors是单独的一个类,可以看成是“线程池工厂”,它有很多静态方法,比如:
- newFixedThreadPool(int nThread)
- newSingleThreadExecutor()
- newCachedThreadPool()
- newSingleThreadScheduledExecutor()
- newScheduledThreadPool(int corePoolSize)
newFixedThreadPool该方法返回一个固定线程数的线程池。当有新任务提交时,如果线程池中有空闲线程就立即执行,否则会进入任务队列中,等到有空闲线程了才能执行。
newSingleThreadExecutor,该方法返回只有一个线程的线程池,处理策略和上面一样。实际上就是上面的参数指定为1而已。
newCachedThreadPool该方法返回一个可根据实际情况调整线程数的线程池,任务提交后,如果有空闲线程可以复用,则优先复用。若线程池中的线程全部在工作,而此时有新任务,则会创建新的线程来处理任务,所有线程执行完后会将线程归还给线程池。
newScheduledThreadPool返回一个ScheduledExecutorService对象,可以有计划地执行任务,比如在某个延时之后开始执行,或者周期性地执行某个任务。可以指定线程数量。
newSingleThreadScheduledExecutor实现了和上面一样的功能,不过线程池的大小为1。
ScheduledExecutorService有三个方法可以有计划地执行任务。如:
-
schedule(Runnable command, long delay, TimeUnit unit);
该方法可以在给定的延时后,执行一个任务; -
scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);
该方法以任务开始执行的时间为initialDelay,加上周期period,就是下一个任务开始执行的时间,以此类推,因此这个方法任务调度的频率是一定的; -
scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);
该方法表示每执行完一个任务,延迟delay
的时间后,开始执行下一个任务,initialDelay
还是表示任务开始的初始时延,上一个任务结束的时间点与下一个任务开始的时间点之差是固定的,固定为delay。
即使单个任务的执行时间超过调度周期,scheduleAtFixedRate也不会让多个任务堆叠,比如任务执行需要8s,而调度周期是2s,调度第二个任务时,第一个还没执行完,因此为了避免任务堆叠,此时调度周期会变成8s;而采用scheduleWithFixedDelay,两个任务之间的实际间隔会变成10s,8s的执行+2s的delay。
Executors是线程池的工厂类,通过调用它的静态方法如
Executors.newCachedThreadPool();
Executors.newFixedThreadPool(n);
可返回一个线程池。这些静态方法统一返回一个ThreadPoolExecutor
,只是参数不同而已。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
包括以上几个参数,其中:
- corePoolSize:指定了线程池中线程的数量;
- maximumPoolSize:线程池中的最大线程数量;
- keepAliveTime:当线程池中线程数量超过corePoolSize时,多余的空闲线程的存活时间;
- unit:上一个参数keepAliveTime的单位
- 任务队列,被提交但还未被执行额任务
- threadFactory:线程工厂,用于创建线程,一般用默认工厂即可。
- handler:拒绝策略。当任务太多来不及处理的时候,采用什么方法拒绝任务。
最重要的是任务队列和拒绝策略。
任务队列主要有ArrayBlockingQueue有界队列、LinkedBlockingQueue*队列、SynchronousQueue直接提交队列。
使用ArrayBlockingQueue,当线程池中实际线程数小于核心线程数时,直接创建线程执行任务;当大于核心线程数而小于最大线程数时,提交到任务队列中;因为这个队列是有界的,当队列满时,在不大于最大线程的前提下,创建线程执行任务;若大于最大线程数,执行拒绝策略。
使用LinkedBlockingQueue时,当线程池中实际线程数小于核心线程数时,直接创建线程执行任务;当大于核心线程数而小于最大线程数时,提交到任务队列中;因为这个队列是有*的,所以之后提交的任务都会进入任务队列中。newFixedThreadPool就采用了*队列,同时指定核心线程和最大线程数一样。
使用SynchronousQueue时,该队列没有容量,对提交任务的不做保存,直接增加新线程来执行任务。newCachedThreadPool使用的是直接提交队列,核心线程数是0,最大线程数是整型的最大值,keepAliveTime是60s,因此当新任务提交时,若没有空闲线程都是新增线程来执行任务,不过由于核心线程数是0,当60s就会回收空闲线程。
当实际线程数超过maxPoolSize时,该采取什么样的策略?
- AbortPolicy:丢弃任务并抛出异常;
- CallerRunPolicy:该任务被线程池拒绝,由调用execute方法的线程执行该任务;
- DiscardOldestPolicy:丢弃最老的一个,也就是马上要执行的一个任务;
- DiscardPolicy:默默丢弃被拒绝的任务,体现在代码中就是什么也不做。
下面看看CallerRunPolicy怎么拒绝的
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
DiscardOldestPolicy是这样做的
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll(); // 最老的一个请求在队列头部
e.execute(r);
}
}
实现一个简单的线程池
实现一个类似于Executors.newFixedThreadPool(n)
的固定大小线程池,当小于corePoolSize时候,优先创建线程去执行该任务;当超过该值时,将任务提交到任务队列中,然后各个线程从任务队列中取任务来执行。
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
public class MyThreadPool {
private int workerCount;
private int corePoolSize;
private BlockingQueue<Runnable> workQueue;
private Set<Worker> workers;
private volatile boolean RUNNING = true;
public MyThreadPool(int corePoolSize) {
this.corePoolSize = corePoolSize;
workQueue = new LinkedBlockingQueue<>();
workers = new HashSet<>();
}
public void execute(Runnable r) {
if (workerCount < corePoolSize) {
addWorker(r);
} else {
try {
workQueue.put(r);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void addWorker(Runnable r) {
workerCount++;
Worker worker = new Worker(r);
Thread t = worker.thread;
workers.add(worker);
t.start();
}
class Worker implements Runnable {
Runnable task;
Thread thread;
public Worker(Runnable task) {
this.task = task;
this.thread = new Thread(this);
}
@Override
public void run() {
while (RUNNING) {
Runnable task = this.task;
// 执行当前的任务,所以把这个任务置空,以免造成死循环
this.task = null;
if (task != null || (task = getTask()) != null) {
task.run();
}
}
}
}
private Runnable getTask() {
Runnable r = null;
try {
r = workQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return r;
}
public static void main(String[] args) {
MyThreadPool threadPool = new MyThreadPool(5);
Runnable r = new Writer();
for (int i = 0; i < 10; i++) {
threadPool.execute(r);
}
}
}
class Writer implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " ");
}
}
Worker实现了Runnale,是真正执行任务的类。当线程池中工作线程小于核心线程时候,调用addWorker直接start线程执行它的第一个任务。否则,将任务放入任务队列中,等线程来执行它们。Worker中的run方法是一个死循环,执行第一个任务(addWorker时调用start方法执行的那个任务),或者通过getTask方法不断从任务队列中取得任务来执行。正是getTask方法实现了线程的复用,即一个线程虽然只能调用一次start方法,但是后续的任务可以在Worker的run方法里直接调用任务的run方法得以执行。简单来说就是在Worker的run里调用任务的run方法。
任务全部执行完毕后,线程池需要被关闭,否则程序一直死循环。上述代码中并没有实现shutdown()
方法。
Java高并发 -- 线程池的更多相关文章
-
Java高并发--线程安全策略
Java高并发--线程安全策略 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 不可变对象 发布不可变对象可保证线程安全. 实现不可变对象有哪些要注意的地方?比如JDK ...
-
JAVA高并发线程
一.JAVA高级并发 1.5JDK之后引入高级并发特性,大多数的特性在java.util.concurrent 包中,是专门用于多线程发编程的,充分利用了现代多处理器和多核心系统的功能以编写大规模并发 ...
-
跟着阿里p7一起学java高并发 - 第18天:玩转java线程池,这一篇就够了
java中的线程池,这一篇就够了 java高并发系列第18篇文章. 本文主要内容 什么是线程池 线程池实现原理 线程池中常见的各种队列 自定义线程创建的工厂 常见的饱和策略 自定义饱和策略 线程池中两 ...
-
【实战Java高并发程序设计 7】让线程之间互相帮助--SynchronousQueue的实现
[实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference [实战Java高并发程序设计 3]带有时间戳的对象 ...
-
java高并发系列 - 第31天:获取线程执行结果,这6种方法你都知道?
这是java高并发系列第31篇. 环境:jdk1.8. java高并发系列已经学了不少东西了,本篇文章,我们用前面学的知识来实现一个需求: 在一个线程中需要获取其他线程的执行结果,能想到几种方式?各有 ...
-
Java高并发与多线程(二)-----线程的实现方式
今天,我们开始Java高并发与多线程的第二篇,线程的实现方式. 通常来讲,线程有三种基础实现方式,一种是继承Thread类,一种是实现Runnable接口,还有一种是实现Callable接口,当然,如 ...
-
Java并发-线程池篇-附场景分析
作者:汤圆 个人博客:javalover.cc 前言 前面我们在创建线程时,都是直接new Thread(): 这样短期来看是没有问题的,但是一旦业务量增长,线程数过多,就有可能导致内存异常OOM,C ...
-
[ 高并发]Java高并发编程系列第二篇--线程同步
高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...
-
Java并发--线程池的使用
在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统 ...
随机推荐
-
PHP开发环境配置~Windows 7 IIS
1.配置IIS添加角色服务:CGI.ISAPI扩展.ISAPI筛选器 2.下载PHP安装包 http://windows.php.net/download/ 3.添加模块映射 4.配置php.ini ...
-
EntityFrame Work:No Entity Framework provider found for the ADO.NET provider with invariant name &#39;System.Data.SqlClient&#39;
今天试着学习了Entity Frame Work遇到的问题是 The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlP ...
-
怎么录制Android视频
有时候我们做了一个Android App想发篇技术文章分享给大家看看效果,该怎么录制这个demo视频呢? 如果你采用的是Android4.4以上版本,可以直接用以下命令来录制视频 adb shell ...
-
poj 题目分类(1)
poj 题目分类 按照ac的代码长度分类(主要参考最短代码和自己写的代码) 短代码:0.01K--0.50K:中短代码:0.51K--1.00K:中等代码量:1.01K--2.00K:长代码:2.01 ...
-
FB面经Prepare: Merge K sorted Array
Merge K sorted Array 跟Merge K sorted lists不同在于,从PQ里poll出来以后不知道下一个需要被加入PQ的是哪一个 所以需要写一个wrapper class p ...
-
如何查看PostgreSQL的checkpoint 活动
磨砺技术珠矶,践行数据之道,追求卓越价值 回到上一级页面:PostgreSQL基础知识与基本操作索引页 回到*页面:PostgreSQL索引页 作者:高健@博客园 luckyjackgao@g ...
-
SMTP——MIME
MIME 基础知识 MIME 表示多用途 Internet 邮件扩允协议.MIME 扩允了基本的面向文本的 Internet 邮件系统,以便可以在消息中包含二进制附件. MIME 信息由正常的 Int ...
-
php array转化为utf-8编码以便于转化为json数据
php中转化为json时,字符串或数组编码必须为utf-8编码. 在网上找到了一个方法可以比较简单的转化,在此记录: 利用var_export()和eval()方法var_export():输出或返回 ...
-
MySQL优化(一):MySQL分库分表
一.分库分表种类 1.垂直拆分 在考虑数据拆分的时候,一般情况下,应该先考虑垂直拆分.垂直可以理解为分出来的库表结构是互相独立各不相同的. - 如果有多个业务,每个业务直接关联性不大,那么就可以把每个 ...
-
jfreechart在jsp中画图方式
这个问题一直困扰我好久,今天算是稍微找到一点解决思路了,在网上搜了好多列子,大部分的都是用servlet来实现画图,偶然找到一个列子用的是org.jfree.chart.servlet.Servlet ...