JUC自定义线程池练习

首先上面该线程池的大致流程

自定义阻塞队列

  • 首先定义一个双向的队列和锁一定两个等待的condition
  • 本类用lock来控制多线程下的流程执行
  • take和push方法就是死等,调用await就是等,后面优化为限时等待
  • take调用后取出阻塞队列的task后会调用fullWaitSet的signal方法来唤醒因为阻塞队列满了的线程将task放入阻塞队列。
@Slf4j
class TaskQueue<T> { // 双向的阻塞队列
private Deque<T> deque;
// 队列最大容量
private int capacity; // 锁
private ReentrantLock lock = new ReentrantLock();
// 消费者任务池空的等待队列
private Condition emptyWaitSet = lock.newCondition();
// 生产者任务池满的等待队列
private Condition fullWaitSet = lock.newCondition(); public TaskQueue(int capacity) {
this.capacity = capacity;
deque = new ArrayDeque<>(capacity);
} // 死等take,即从阻塞队列取出任务
public T take() {
lock.lock();
try {
while (deque.isEmpty()) {
try {
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("取走任务");
T task = deque.pollFirst();
fullWaitSet.signal();
return task;
} finally {
lock.unlock();
}
} // 线程添加任务,属于是死等添加
public void push(T task) {
lock.lock();
try {
while (deque.size() >= capacity) {
try {
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("添加任务");
deque.offerLast(task);
emptyWaitSet.signal();
} finally {
lock.unlock();
}
} public int getSize() {
lock.lock();
try {
return deque.size();
}finally {
lock.unlock();
}
} }

优化,死等优化为超时等

  • awaitNanos方法返回的是等待的剩余时间,如果已经等了base时间就会返回0,如果没有就会返回大于0即还没有等待的时间,防止虚假唤醒导致重新等待时间加长。当然在本题的设计中不会出现虚假唤醒的情况。
public T poll(Long timeout,TimeUnit unit) {
lock.lock();
try {
long base = unit.toNanos(timeout);
while (deque.isEmpty()) {
try {
if (base <= 0){
return null;
}
base = emptyWaitSet.awaitNanos(base); // 返回还剩下的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("取走任务");
T task = deque.pollFirst();
fullWaitSet.signal();
return task;
} finally {
lock.unlock();
}
}

线程池类

  • 成员变量如下,对于Worker就工作线程
@Slf4j
class ThreadPool {
// 阻塞队列大小
private int capacity;
// 阻塞队列
private TaskQueue<Runnable> taskQueue;
// 工作线程
private HashSet<Worker> workerSet = new HashSet<>();
// 核心数
private int coreNum;
// 超时等待时间
private long timeout;
// 超时等待单位
private TimeUnit unit;
// 拒绝策略
private RejectPolicy rejectPolicy; // 线程对象
class Worker extends Thread { private Runnable task; public Worker(Runnable runnable) {
this.task = runnable;
} @Override
public void run() {
// 就是线程把当前分配的任务做完,然后还要去阻塞队列找活干,没活就退出
// taks 如果不为空就执行然后讲其置为空,后续再次进入循环后会从阻塞队列中再次取出task,
// 如果不为空就继续执行,但是因为take死等,会导致无法结束
// 使用了这个超时等的方法,当无法取出时就会退出程序
while (task != null || (task = taskQueue.poll(timeout,unit)) != null) {
try {
log.debug("开始执行任务");
Thread.sleep(1000);
task.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
task = null;
}
}
// 当没有任务可执行,线程自动销毁,由于这是根据对象来销毁,且hashset无序,所以这里无需保证其的线程安全。
workerSet.remove(this);
}
} public ThreadPool(int capacity, int coreNum, long timeout, TimeUnit unit,RejectPolicy rejectPolicy) {
this.capacity = capacity;
this.coreNum = coreNum;
this.timeout = timeout;
this.unit = unit;
this.taskQueue = new TaskQueue<>(capacity);
this.rejectPolicy = rejectPolicy;
} /**
* 当线程数大于核心数,就将任务放入阻塞队列
* 否则创建线程进行处理
*
* @param runnable
*/
public void execute(Runnable runnable) {
// 需要synchronized关键字控制多线程下对执行方法的执行,保证共享变量workerSet安全。
synchronized (workerSet) {
// 如果已经存在的工作线程已经大于核心数,就不适合在进行创建线程了,创太多线程对于执行并不会加快,反而会因为线程不断切换而拖累CPU的执行。
if (workerSet.size() >= coreNum) {
taskQueue.push(runnable);
} else {
// 如果工作线程小于核心数就可创建一个worker线程来工作
Worker worker = new Worker(runnable);
workerSet.add(worker);
worker.start();
}
}
}
}

测试类

@Slf4j
public class MyThreadPool { public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(3,2,1,TimeUnit.SECONDS,(taskQueue,task)->{
taskQueue.push(task);
});
for (int i = 0; i < 10; i++) {
int j = i;
threadPool.execute(() -> {
log.debug("任务{}", j);
});
}
} }

优化---拒绝策略

我们没有进行优化的就是当任务太多导致阻塞线程也满了,此时任务线程就会进行阻塞,直到等到有人在线程池中取走任务。也就是push方法,我们在旧的方法中仍采用的是死等的方法。

但是方法中有很多死等,超时等,放弃任务,抛出异常,让调用者自己执行任务等等方法。

我们就可用讲其进行抽象,把操作交给调用者。

定义了如下的函数式接口,即为拒绝策略。

@FunctionalInterface
interface RejectPolicy<T>{
void reject(TaskQueue<T> taskQueue,T task);
}

将在TaskQueue任务队列中定义不同的策略,我们只要传入这个函数式接口的实现对象就可用实现定制拒绝的策略。

在TaskQueue类添加一个方法,用来调用拒绝策略

public void tryAndAdd(T task,RejectPolicy rejectPolicy){
lock.lock();
try {
if (deque.size() >= capacity) {
rejectPolicy.reject(this,task);
}else{
log.debug("添加任务");
deque.offerLast(task);
emptyWaitSet.signal();
}
} finally {
lock.unlock();
}
}

更改了构造方法的线程池类,这样就可用传入一个自定义的拒绝策略。

@Slf4j
class ThreadPool {
// 阻塞队列大小
private int capacity;
// 阻塞队列
private TaskQueue<Runnable> taskQueue;
// 工作线程
private HashSet<Worker> workerSet = new HashSet<>();
// 核心数
private int coreNum;
// 超时等待时间
private long timeout;
// 超时等待单位
private TimeUnit unit;
// 拒绝策略
private RejectPolicy rejectPolicy; // 线程对象
class Worker extends Thread { private Runnable task; public Worker(Runnable runnable) {
this.task = runnable;
} @Override
public void run() {
while (task != null || (task = taskQueue.poll(timeout,unit)) != null) {
try {
log.debug("开始执行任务");
Thread.sleep(1000);
task.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
task = null;
}
}
workerSet.remove(this);
}
} public ThreadPool(int capacity, int coreNum, long timeout, TimeUnit unit,RejectPolicy rejectPolicy) {
this.capacity = capacity;
this.coreNum = coreNum;
this.timeout = timeout;
this.unit = unit;
this.taskQueue = new TaskQueue<>(capacity);
this.rejectPolicy = rejectPolicy;
} /**
* 当线程数大于核心数,就将任务放入阻塞队列
* 否则创建线程进行处理
*
* @param runnable
*/
public void execute(Runnable runnable) {
synchronized (workerSet) {
if (workerSet.size() >= coreNum) {
taskQueue.tryAndAdd(runnable,rejectPolicy);
} else {
Worker worker = new Worker(runnable);
workerSet.add(worker);
worker.start();
}
}
}
}

将启动类修改如下

@Slf4j
public class MyThreadPool { public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(3,2,1,TimeUnit.SECONDS,(taskQueue,task)->{
// 采用死等的方法,当然我们可用在taskQueue中定义更多的方法让调用者选择
taskQueue.push(task);
});
for (int i = 0; i < 10; i++) {
int j = i;
threadPool.execute(() -> {
log.debug("任务{}", j);
});
}
} }

这样我们就完成了自定义的线程池。

JUC自定义线程池练习的更多相关文章

  1. Android线程管理之ThreadPoolExecutor自定义线程池

    前言: 上篇主要介绍了使用线程池的好处以及ExecutorService接口,然后学习了通过Executors工厂类生成满足不同需求的简单线程池,但是有时候我们需要相对复杂的线程池的时候就需要我们自己 ...

  2. Android AsyncTask 深度理解、简单封装、任务队列分析、自定义线程池

    前言:由于最近在做SDK的功能,需要设计线程池.看了很多资料不知道从何开始着手,突然发现了AsyncTask有对线程池的封装,so,就拿它开刀,本文将从AsyncTask的基本用法,到简单的封装,再到 ...

  3. Android 自定义线程池的实战

    前言:在上一篇文章中我们讲到了AsyncTask的基本使用.AsyncTask的封装.AsyncTask 的串行/并行线程队列.自定义线程池.线程池的快速创建方式. 对线程池不了解的同学可以先看 An ...

  4. c#网络通信框架networkcomms内核解析之十 支持优先级的自定义线程池

    NetworkComms网络通信框架序言 本例基于networkcomms2.3.1开源版本  gplv3协议 如果networkcomms是一顶皇冠,那么CommsThreadPool(自定义线程池 ...

  5. 介绍开源的.net通信框架NetworkComms框架 源码分析(十五 ) CommsThreadPool自定义线程池

    原文网址: http://www.cnblogs.com/csdev Networkcomms 是一款C# 语言编写的TCP/UDP通信框架  作者是英国人  以前是收费的 目前作者已经开源  许可是 ...

  6. 一个自定义线程池的小Demo

    在项目中如果是web请求时候,IIS会自动分配一个线程来进行处理,如果很多个应用程序共享公用一个IIS的时候,线程分配可能会出现一个问题(当然也是我的需求造成的) 之前在做项目的时候,有一个需求,就是 ...

  7. C#自定义线程池

    自定义线程池-c#的简单实现 下面是代码,希望大家提出更好的建议: 1.ThreadManager.cs using System; using System.Threading; using Sys ...

  8. JAVA并发,线程工厂及自定义线程池

    package com.xt.thinks21_2; import java.util.concurrent.ExecutorService; import java.util.concurrent. ...

  9. java多线程(四)-自定义线程池

    当我们使用 线程池的时候,可以使用 newCachedThreadPool()或者 newFixedThreadPool(int)等方法,其实我们深入到这些方法里面,就可以看到它们的是实现方式是这样的 ...

随机推荐

  1. Python turtle 模块可以编写游戏,是真的吗?

    1. 前言 turtle (小海龟) 是 Python 内置的一个绘图模块,其实它不仅可以用来绘图,还可以制作简单的小游戏,甚至可以当成简易的 GUI 模块,编写简单的 GUI 程序. 本文使用 tu ...

  2. CSS学习(二):背景图片如何定位?

    我们都知道background-position属性用来指定背景图片应该出现的位置,可以使用关键字.绝对值和相对值进行指定.在CSS Sprites中,这个属性使用比较频繁,使用过程中,我常混淆,经常 ...

  3. 【分享】WeX5的正确打开方式(6)——数据组件初探

    本文是[WeX5的正确打开方式]系列的第6篇文章,简单介绍一下WeX5中数据组件的特性和结构形式. 数据组件的由来 上一篇 WeX5绑定机制我们实现了一个简单的记账本应用,当时所有数据都用 JSON ...

  4. PC端免费高效的同声翻译

    疫情期间上网课,对于英语听力较差或者需要观看英文视频,但实际上并没有双语字幕的这种情况下需要找一个实时的翻译工具.虽然说手机上此类软件比较多,但电脑上没有特别合适的应用可以做为一个免费实时翻译.哪怕是 ...

  5. PAT B1091 N-自守数

    输入样例: 3 92 5 233   输出样例: 3 25392 1 25 No '解题思路:判断的时候将结果转换成字符串,判断后面几位数字和输入数字是否相同,掉进了N是从1到10的坑,而不是1到9 ...

  6. PAT B1031查验身份证

    一个合法的身份证号码由17位地区.日期编号和顺序编号加1位校验码组成.校验码的计算规则如下: 首先对前17位数字加权求和,权重分配为:{7,9,10,5,8,4,2,1,6,3,7,9,10,5,8, ...

  7. Jackson 和 fastJSON 导包异常

    内容 一.异常信息 HTTP Status 400 - type Status report message org.springframework.http.converter.HttpMessag ...

  8. java基础-File

    File类 * File更应该叫做一个路径, 文件路径或者文件夹路径    * 路径分为绝对路径和相对路径  * 绝对路径是一个固定的路径,从盘符开始  * 相对路径相对于某个位置,在eclipse下 ...

  9. jboss学习1之EJB和JBOSS的宏观理解

    一.中间件(Middleware)         先来看一张图:         中间件,也就是图中的Middleware,他的作用是什么呢?        简单来说,中间件就是操作系统和应用程序之 ...

  10. Array.fill()函数的用法

    ES6,Array.fill()函数的用法   ES6为Array增加了fill()函数,使用制定的元素填充数组,其实就是用默认内容初始化数组. 该函数有三个参数. arr.fill(value, s ...