在之前介绍了线程池的快捷创建方式以及其中的隐患,紧接着介绍了线程池的标准创建方式,了解了线程池的基本构造后,我们可以自己尝试手写一个简单的线程池来加深我们对线程池的理解。
首先我们需要一个阻塞队列,用来存放任务,可以向其中添加任务,获取其中的任务,还要实现当队列为空时,不能获取任务,当队列已满时,不能向其中添加任务。
/**
* 自定义阻塞队列类
* @param
*/
class BlockingQueueTest<T> {
java.util.logging.Logger logger= getLogger("BlockingQueueTest");
// 1. 任务队列
private final Deque<T> deque = new ArrayDeque<>();
// 2. 锁
private final ReentrantLock lock = new ReentrantLock();
// 3. 生产者条件
private final Condition addCon = lock.newCondition();
// 4. 消费者条件
private final Condition delCon = lock.newCondition();
// 5. 队列容量
private final int len;
/**
* 构造方法,指定队列的最大容量
* @param len
*/
public BlockingQueueTest(int len) {
this.len = len;
}
/**
* 判断阻塞队列是否已满
* @return
*/
public boolean isTrue(){
return size()==len;
}
/**
* 阻塞获取队列中的任务
*/
public T get(){
lock.lock();
try {
while(deque.isEmpty()){
try {
delCon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = deque.getFirst();
deque.removeFirst();
addCon.signal();
return t;
}finally {
lock.unlock();
}
}
/**
* 有超时时间的阻塞获取队列中的任务
* @param time
* @param timeUnit
*/
public T poll(Long time, TimeUnit timeUnit){
lock.lock();
try {
//将等待时间统一转换为纳秒
long nanos = timeUnit.toNanos(time);
while(deque.isEmpty()){
try {
if(nanos <= 0){
return null;
}
//返回的值为等待一段时间后剩余的时间
nanos = delCon.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = deque.getFirst();
deque.removeFirst();
addCon.signal();
return t;
}finally {
lock.unlock();
}
}
/**
* 阻塞添加任务
* @param task
*/
public void put(T task){
lock.lock();
try{
while(deque.size()==len){
try {
logger.log(Level.INFO,task+"等待加入任务队列---------");
addCon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
logger.log(Level.INFO,task+"加入任务队列========");
deque.addLast(task);
delCon.signal();
}finally {
lock.unlock();
}
}
/**
* 带超时时间的添加任务
* @param time
* @param unit
* @return
*/
public boolean offer(T task,Long time,TimeUnit unit){
lock.lock();
try{
long nanos = unit.toNanos(time);
while(deque.size()==len){
try {
logger.log(Level.INFO,task+"等待加入任务队列---------");
if(nanos<=0){
return false;
}
nanos = addCon.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
logger.log(Level.INFO,task+"加入任务队列========");
deque.addLast(task);
delCon.signal();
return true;
}finally {
lock.unlock();
}
}
/**
* 获取队列的容量
* @return
*/
public int size(){
lock.lock();
try{
return deque.size();
}finally {
lock.unlock();
}
}
/**
* 带拒绝策略的添加任务
* @param rejectPoilicy
* @param task
*/
public void tryPut(RejectPoilicy<T> rejectPoilicy, T task) {
lock.lock();
try{
//判断队列是否已满
if(deque.size()==len){
rejectPoilicy.reject(this,task);
}else {
logger.log(Level.INFO,task+"加入任务队列========");
deque.addLast(task);
delCon.signal();
}
}finally {
lock.unlock();
}
}
}
由于拒绝策略有好几种实现方案,这里我们使用定义一个拒绝策略的接口,让调用者使用函数式编程或Lambda表达式来自己实现其中的方法。定义的接口如下:
/**
* 拒绝策略接口
* @param
*/
@FunctionalInterface
interface RejectPoilicy<T>{
void reject(BlockingQueueTest<T> queue,T task);
}
这里我没有单独写一个线程工厂,而是在线程池的里面定义了一个内部类,来执行提交的任务,同时在里面通过名字来核心线程与普通线程,这决定了他们在执行完任务后会不会结束。
//原子类
AtomicInteger threadNo = new AtomicInteger(1);
/**
* 工作线程类
*/
class Worker extends Thread{
private Runnable task;
private String threadName;
private Worker(Runnable task) {
this.task=task;
setThreadName();
this.setName(threadName);
logger.log(Level.INFO,threadName+"已创建..........");
}
private void setThreadName(){
threadName = "simpleThread-" + threadNo.get();
threadNo.incrementAndGet();
}
@Override
public void run() {
char c = threadName.charAt(13);
if((c-'0')<=coreSize){//对比线程编号,小于核心线程数的线程调用阻塞执行任务,执行结束后不移出线程队列
gorun();
}else{//大于核心线程数的采用限时等待执行,执行结束后移除出线程队列
go();
}
}
/**
* 普通线程执行完任务后限时等待任务队列,移除
*/
private void go(){
while(task != null || (task = blockingQueueTest.poll(outTime,unit)) != null){
try {
logger.log(Level.INFO,"普通线程"+threadName+"正在执行"+task+"任务....................");
task.run();
}catch (Exception e){
e.printStackTrace();
}finally {
task = null;
}
}
synchronized (workers){
logger.log(Level.INFO,"*******worker被移除"+this);
workers.remove(this);
}
}
/**
* 核心线程执行完任务后阻塞等待任务队列,不移除
*/
private void gorun(){
while(task != null || (task = blockingQueueTest.get()) != null){
try {
logger.log(Level.INFO,"核心线程"+threadName+"正在执行"+task+"任务....................");
task.run();
}catch (Exception e){
e.printStackTrace();
}finally {
task = null;
}
}
}
}
关于线程池要使用的基本都已经编写完,接下来编写线程池类。
/**
* 自定义线程池类
*/
class ThreadPool{
java.util.logging.Logger logger= getLogger("ThreadPool");
//阻塞任务队列
private final BlockingQueueTest<Runnable> blockingQueueTest;
//线程集合
private final HashSet<Worker> workers = new HashSet<>();
//核心线程数
private final int coreSize;
//最大线程数
private final int maxThreadSize;
//超时时间
private final long outTime;
//时间单位
private final TimeUnit unit;
//原子类
AtomicInteger threadNo = new AtomicInteger(1);
//线程工厂
// private final ThreadFactory threadFactory;
//拒绝策略
private final RejectPoilicy<Runnable> rejectPoilicy;
public ThreadPool(int coreSize,int maxThreadSize, long outTime, TimeUnit unit, int queueSize, RejectPoilicy<Runnable> rejectPoilicy) {
this.coreSize = coreSize;
this.maxThreadSize=maxThreadSize;
this.outTime = outTime;
this.unit = unit;
this.blockingQueueTest = new BlockingQueueTest<>(queueSize);
// this.threadFactory=threadFactory;
this.rejectPoilicy = rejectPoilicy;
logger.log(Level.INFO,"====================ThreadPool初始化成功=================");
}
public void execute(Runnable task){
synchronized (workers){
//如果任务数没有超过核心线程数,直接交给worker执行
if(workers.size()<coreSize) {
Worker worker = new Worker(task);
workers.add(worker);
logger.log(Level.INFO, "=============================新增worker:" + worker);
logger.log(Level.INFO, "=============新增task:" + task);
worker.start();
}else if(!blockingQueueTest.isTrue()) {
//如果线程数达到核心线程数,阻塞队列不满,则将任务加入任务队列
blockingQueueTest.put(task);
}else if(workers.size()<maxThreadSize){
//如果阻塞队列已满,单线程数小于最大线程数,则立即创建线程执行该任务
Worker worker = new Worker(task);
workers.add(worker);
logger.log(Level.INFO, "=============================新增worker:" + worker);
logger.log(Level.INFO, "=============新增task:" + task);
worker.start();
}else{
//线程数大于最大线程数,为其执行拒绝策略
blockingQueueTest.tryPut(rejectPoilicy,task);
}
}
}
/**
* 工作线程类
*/
//.....
}
接下来编写一个测试类来实验一下:
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import static java.util.logging.Logger.getLogger;
public class TestPool {
public static void main(String[] args) {
java.util.logging.Logger logger= getLogger("TestPool");
ThreadPool pool = new ThreadPool(2,4, 1, TimeUnit.SECONDS,
3, (queue,task)->{
/**
* 1.阻塞等待加入等待队列
* queue.put(task);
* 一直等待加入队列
* 2.带等待时间的等待加入
* queue.offer(task, 1500L, TimeUnit.MILLISECONDS);
* 在预期时间内等待,超时则退出等待
* 3.让调用者放弃任务执行
* logger.log(Level.INFO,"^^^^^^^^^^^^放弃执行"+task+"了^^^^^^^");
* 放弃无法加入等待队列的任务
* 4.让调用者抛出异常
* throw new RuntimeException("任务执行失败"+task);
* 之后的代码不会执行
* 5.让调用者自己执行任务
* task.run();
*/
queue.put(task);
});
for (int i = 0; i < 10; i++) {
int j=i;
pool.execute(()-> {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.log(Level.INFO, String.valueOf(j));
});
}
}
}
五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool
信息: ====ThreadPool初始化成功=
五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool W o r k e r < i n i t > 信息 : s i m p l e T h r e a d − 1 已创建 . . . . . . . . . . 五月 20 , 202310 : 00 : 22 下午 s u a n . J U C . T h r e a d P o o l e x e c u t e 信息 : = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 新增 w o r k e r : T h r e a d [ s i m p l e T h r e a d − 1 , 5 , m a i n ] 五月 20 , 202310 : 00 : 22 下午 s u a n . J U C . T h r e a d P o o l e x e c u t e 信息 : = = = = = = = = = = = = = 新增 t a s k : s u a n . J U C . T e s t P o o l Worker信息: simpleThread-1已创建.......... 五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool execute 信息: =============================新增worker:Thread[simpleThread-1,5,main] 五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool execute 信息: =============新增task:suan.JUC.TestPool Worker<init>信息:simpleThread−1已创建..........五月20,202310:00:22下午suan.JUC.ThreadPoolexecute信息:=============================新增worker:Thread[simpleThread−1,5,main]五月20,202310:00:22下午suan.JUC.ThreadPoolexecute信息:=============新增task:suan.JUC.TestPool$Lambda 2 / 1072408673 @ b 4 c 966 a 五月 20 , 202310 : 00 : 22 下午 s u a n . J U C . T h r e a d P o o l 2/1072408673@b4c966a 五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool 2/1072408673@b4c966a五月20,202310:00:22下午suan.JUC.ThreadPoolWorker
信息: simpleThread-2已创建…
五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool W o r k e r g o r u n 信息 : 核心线程 s i m p l e T h r e a d − 1 正在执行 s u a n . J U C . T e s t P o o l Worker gorun 信息: 核心线程simpleThread-1正在执行suan.JUC.TestPool Workergorun信息:核心线程simpleThread−1正在执行suan.JUC.TestPool$Lambda 2 / 1072408673 @ b 4 c 966 a 任务 . . . . . . . . . . . . . . . . . . . . 五月 20 , 202310 : 00 : 22 下午 s u a n . J U C . T h r e a d P o o l e x e c u t e 信息 : = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 新增 w o r k e r : T h r e a d [ s i m p l e T h r e a d − 2 , 5 , m a i n ] 五月 20 , 202310 : 00 : 22 下午 s u a n . J U C . T h r e a d P o o l e x e c u t e 信息 : = = = = = = = = = = = = = 新增 t a s k : s u a n . J U C . T e s t P o o l 2/1072408673@b4c966a任务.................... 五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool execute 信息: =============================新增worker:Thread[simpleThread-2,5,main] 五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool execute 信息: =============新增task:suan.JUC.TestPool 2/1072408673@b4c966a任务....................五月20,202310:00:22下午suan.JUC.ThreadPoolexecute信息:=============================新增worker:Thread[simpleThread−2,5,main]五月20,202310:00:22下午suan.JUC.ThreadPoolexecute信息:=============新增task:suan.JUC.TestPool$Lambda 2 / 1072408673 @ 4 e 50 d f 2 e 五月 20 , 202310 : 00 : 22 下午 s u a n . J U C . B l o c k i n g Q u e u e T e s t p u t 信息 : s u a n . J U C . T e s t P o o l 2/1072408673@4e50df2e 五月 20, 2023 10:00:22 下午 suan.JUC.BlockingQueueTest put 信息: suan.JUC.TestPool 2/1072408673@4e50df2e五月20,202310:00:22下午suan.JUC.BlockingQueueTestput信息:suan.JUC.TestPool$Lambda 2 / 1072408673 @ 1 d 81 e b 93 加入任务队列 = = = = = = = = 五月 20 , 202310 : 00 : 22 下午 s u a n . J U C . T h r e a d P o o l 2/1072408673@1d81eb93加入任务队列======== 五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool 2/1072408673@1d81eb93加入任务队列========五月20,202310:00:22下午suan.JUC.ThreadPoolWorker gorun
信息: 核心线程simpleThread-2正在执行suan.JUC.TestPoolKaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲2/1072408673@4e…Lambda 2 / 1072408673 @ 7291 c 18 f 加入任务队列 = = = = = = = = 五月 20 , 202310 : 00 : 22 下午 s u a n . J U C . B l o c k i n g Q u e u e T e s t p u t 信息 : s u a n . J U C . T e s t P o o l 2/1072408673@7291c18f加入任务队列======== 五月 20, 2023 10:00:22 下午 suan.JUC.BlockingQueueTest put 信息: suan.JUC.TestPool 2/1072408673@7291c18f加入任务队列========五月20,202310:00:22下午suan.JUC.BlockingQueueTestput信息:suan.JUC.TestPool$Lambda 2 / 1072408673 @ 34 a 245 a b 加入任务队列 = = = = = = = = 五月 20 , 202310 : 00 : 22 下午 s u a n . J U C . T h r e a d P o o l 2/1072408673@34a245ab加入任务队列======== 五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool 2/1072408673@34a245ab加入任务队列========五月20,202310:00:22下午suan.JUC.ThreadPoolWorker
信息: simpleThread-3已创建…
五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool execute
信息: =============================新增worker:Thread[simpleThread-3,5,main]
五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool execute
信息: =============新增task:suan.JUC.TestPoolKaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲2/1072408673@6e…Lambda 2 / 1072408673 @ 6 e 8 c f 4 c 6 任务 . . . . . . . . . . . . . . . . . . . . 五月 20 , 202310 : 00 : 22 下午 s u a n . J U C . T h r e a d P o o l e x e c u t e 信息 : = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 新增 w o r k e r : T h r e a d [ s i m p l e T h r e a d − 4 , 5 , m a i n ] 五月 20 , 202310 : 00 : 22 下午 s u a n . J U C . T h r e a d P o o l e x e c u t e 信息 : = = = = = = = = = = = = = 新增 t a s k : s u a n . J U C . T e s t P o o l 2/1072408673@6e8cf4c6任务.................... 五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool execute 信息: =============================新增worker:Thread[simpleThread-4,5,main] 五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool execute 信息: =============新增task:suan.JUC.TestPool 2/1072408673@6e8cf4c6任务....................五月20,202310:00:22下午suan.JUC.ThreadPoolexecute信息:=============================新增worker:Thread[simpleThread−4,5,main]五月20,202310:00:22下午suan.JUC.ThreadPoolexecute信息:=============新增task:suan.JUC.TestPool$Lambda 2 / 1072408673 @ 34 c 45 d c a 五月 20 , 202310 : 00 : 22 下午 s u a n . J U C . B l o c k i n g Q u e u e T e s t p u t 信息 : s u a n . J U C . T e s t P o o l 2/1072408673@34c45dca 五月 20, 2023 10:00:22 下午 suan.JUC.BlockingQueueTest put 信息: suan.JUC.TestPool 2/1072408673@34c45dca五月20,202310:00:22下午suan.JUC.BlockingQueueTestput信息:suan.JUC.TestPool$Lambda 2 / 1072408673 @ 52 c c 8049 等待加入任务队列 − − − − − − − − − 五月 20 , 202310 : 00 : 22 下午 s u a n . J U C . T h r e a d P o o l 2/1072408673@52cc8049等待加入任务队列--------- 五月 20, 2023 10:00:22 下午 suan.JUC.ThreadPool 2/1072408673@52cc8049等待加入任务队列−−−−−−−−−五月20,202310:00:22下午suan.JUC.ThreadPoolWorker go
信息: 普通线程simpleThread-4正在执行suan.JUC.TestPoolKaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲2/1072408673@34…Lambda 2 / 1072408673 @ 52 c c 8049 加入任务队列 = = = = = = = = 五月 20 , 202310 : 00 : 23 下午 s u a n . J U C . T h r e a d P o o l 2/1072408673@52cc8049加入任务队列======== 五月 20, 2023 10:00:23 下午 suan.JUC.ThreadPool 2/1072408673@52cc8049加入任务队列========五月20,202310:00:23下午suan.JUC.ThreadPoolWorker go
信息: 普通线程simpleThread-3正在执行suan.JUC.TestPoolKaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲2/1072408673@1d…Lambda 2 / 1072408673 @ 27973 e 9 b 等待加入任务队列 − − − − − − − − − 五月 20 , 202310 : 00 : 23 下午 s u a n . J U C . T h r e a d P o o l 2/1072408673@27973e9b等待加入任务队列--------- 五月 20, 2023 10:00:23 下午 suan.JUC.ThreadPool 2/1072408673@27973e9b等待加入任务队列−−−−−−−−−五月20,202310:00:23下午suan.JUC.ThreadPoolWorker gorun
信息: 核心线程simpleThread-1正在执行suan.JUC.TestPoolKaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲2/1072408673@72…Lambda 2 / 1072408673 @ 27973 e 9 b 加入任务队列 = = = = = = = = 五月 20 , 202310 : 00 : 23 下午 s u a n . J U C . T h r e a d P o o l 2/1072408673@27973e9b加入任务队列======== 五月 20, 2023 10:00:23 下午 suan.JUC.ThreadPool 2/1072408673@27973e9b加入任务队列========五月20,202310:00:23下午suan.JUC.ThreadPoolWorker gorun
信息: 核心线程simpleThread-2正在执行suan.JUC.TestPoolKaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲2/1072408673@52…Lambda 2 / 1072408673 @ 34 a 245 a b 任务 . . . . . . . . . . . . . . . . . . . . 五月 20 , 202310 : 00 : 23 下午 s u a n . J U C . B l o c k i n g Q u e u e T e s t p u t 信息 : s u a n . J U C . T e s t P o o l 2/1072408673@34a245ab任务.................... 五月 20, 2023 10:00:23 下午 suan.JUC.BlockingQueueTest put 信息: suan.JUC.TestPool 2/1072408673@34a245ab任务....................五月20,202310:00:23下午suan.JUC.BlockingQueueTestput信息:suan.JUC.TestPool$Lambda 2 / 1072408673 @ 312 b 1 d a e 加入任务队列 = = = = = = = = 五月 20 , 202310 : 00 : 24 下午 s u a n . J U C . T e s t P o o l l a m b d a 2/1072408673@312b1dae加入任务队列======== 五月 20, 2023 10:00:24 下午 suan.JUC.TestPool lambda 2/1072408673@312b1dae加入任务队列========五月20,202310:00:24下午suan.JUC.TestPoollambdamain 1 信息 : 2 五月 20 , 202310 : 00 : 24 下午 s u a n . J U C . T h r e a d P o o l 1 信息: 2 五月 20, 2023 10:00:24 下午 suan.JUC.ThreadPool 1信息:2五月20,202310:00:24下午suan.JUC.ThreadPoolWorker go
信息: 普通线程simpleThread-3正在执行suan.JUC.TestPoolKaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲2/1072408673@27…Lambda 2 / 1072408673 @ 312 b 1 d a e 任务 . . . . . . . . . . . . . . . . . . . . 五月 20 , 202310 : 00 : 25 下午 s u a n . J U C . T e s t P o o l l a m b d a 2/1072408673@312b1dae任务.................... 五月 20, 2023 10:00:25 下午 suan.JUC.TestPool lambda 2/1072408673@312b1dae任务....................五月20,202310:00:25下午suan.JUC.TestPoollambdamain 1 信息 : 8 五月 20 , 202310 : 00 : 25 下午 s u a n . J U C . T e s t P o o l l a m b d a 1 信息: 8 五月 20, 2023 10:00:25 下午 suan.JUC.TestPool lambda 1信息:8五月20,202310:00:25下午suan.JUC.TestPoollambdamain 1 信息 : 9 五月 20 , 202310 : 00 : 25 下午 s u a n . J U C . T h r e a d P o o l 1 信息: 9 五月 20, 2023 10:00:25 下午 suan.JUC.ThreadPool 1信息:9五月20,202310:00:25下午suan.JUC.ThreadPoolWorker go
信息: *******worker被移除Thread[simpleThread-4,5,main]
五月 20, 2023 10:00:26 下午 suan.JUC.ThreadPool$Worker go
信息: *******worker被移除Thread[simpleThread-3,5,main]
成功,且由于核心线程并没有结束,所以我们的进程并没有结束。
以上就是手写一个线程类的思路代码,另外这只是一个简单的实现,很多功能向关闭线程池的功能就没有编写,仅仅作为加深线程池的理解的一种方式,如有错误或可改进的地方,欢迎指正!