Java多线程 -- 动手实现线程池

动手实现线程池

1. 什么是线程池

线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。由于创建和销毁线程都是消耗系统资源的,所以当你想要频繁的创建和销毁线程的时候就可以考虑使用线程池来提升系统的性能。

2. 线程池原理图

Thread Pool : 存放线程的集合

Blocking Queue : 任务队列

3.手写一个简单的线程池

1. 实现自定义任务队列
@Slf4j
class BlockingQueue<T> {
    // 1. 任务队列 -- 存放任务
    private Deque<T> queue = new ArrayDeque<>();

    // 2. 锁
    private ReentrantLock lock = new ReentrantLock();

    // 3. 生产者条件变量 -- 当任务队列满时, 停止生产
    private Condition fullWaitSet = lock.newCondition();

    // 4. 消费者条件变量 -- 当任务队列为空时, 停止消费
    private Condition emptyWaitSet = lock.newCondition();

    // 5. 容量 -- 定义任务队列容量
    private int capcity;

    // 6. 初始化任务队列, 固定任务队列容量 
    public BlockingQueue(int capcity) {
        this.capcity = capcity;
    }
    
    // 7. 阻塞获取
    public T take() {
        lock.lock(); // 加锁
        try {
            while (queue.isEmpty()) { // 判断任务队列是否为空, 为空则等待, 不为空跳出循环
                try {
                    emptyWaitSet.await(); // 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = queue.removeFirst(); // 获取任务 -- 从头部获取
            fullWaitSet.signal(); // 唤醒(任务队列为满时, 阻塞添加方法, 使其继续添加任务)
            return t; // 返回获取的任务
        } finally {
            lock.unlock(); // 释放锁
        }
    }
    
    // 8. 阻塞添加
    public void put(T task) {
        lock.lock(); // 加锁
        try {
            while (queue.size() == capcity) { // 判断任务队列是否已满, 为满则等待, 不为满跳出循环
                try {
                    fullWaitSet.await(); // 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            queue.addLast(task); // 存入任务队列 -- 从尾部加入
            emptyWaitSet.signal(); // 唤醒(任务队列为空时, 阻塞获取方法, 使其继续获取任务)
        } finally {
            lock.unlock();
        }
    }

}

上面即是简单的任务队列实现,但是我们能够发现:当队列为空或满时,会陷入等待,直到被唤醒。

因此我们可以升级下阻塞获取阻塞添加方法 – > 带超时阻塞获取带超时阻塞添加

// 带超时阻塞获取
public T poll(long timeout, TimeUnit unit) {
    lock.lock();
    try {
        // 将 timeout 统一转换为 纳秒
        long nanos = unit.toNanos(timeout);
        while (queue.isEmpty()) {
            try {
                // 返回值(nanos)是剩余时间, 当剩余时间小于0时退出
                if (nanos <= 0) {
                    return null;
                }
                nanos = emptyWaitSet.awaitNanos(nanos);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        T t = queue.removeFirst();
        fullWaitSet.signal();
        return t;
    } finally {
        lock.unlock();
    }
}

// 带超时阻塞添加
public boolean offer(T task, long timeout, TimeUnit timeUnit) {
    lock.lock();
    try {
        // 将 timeout 统一转换为 纳秒
        long nanos = timeUnit.toNanos(timeout);
        while (queue.size() == capcity) {
            try {
                // 返回值(nanos)是剩余时间, 当剩余时间小于0时退出
                if (nanos < 0) {
                    return false;
                }
                nanos = fullWaitSet.awaitNanos(nanos);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        queue.addLast(task);
        emptyWaitSet.signal();
        return true;
    } finally {
        lock.unlock();
    }
}

此时任务队列基本完成

2. 实现自定义线程池
@Slf4j(topic = "log.ThreadPool")
class ThreadPool {
    // 1. 任务队列
    private BlockingQueue<Runnable> taskQueue;

    // 2. 线程集合 -- 存放线程
    private HashSet<Worker> workers = new HashSet<>();

    // 3. 核心线程数 -- 线程数量
    private int coreSize;

    // 4. 获取任务时的超时时间
    private long timeout;

    // 5. 用于指定时间单位, 传入taskQueue中的超时等待方法
    private TimeUnit timeUnit;
    
    // 6. 构造方法
    public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapcity) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.taskQueue = new BlockingQueue<>(queueCapcity);
    }
    
    // 7. 内部类, 继承Thread, 用于实现从taskQueue获取的任务
    class Worker extends Thread {
		// 获取的任务
        private Runnable task;
        
        // 构造方法, 传入任务
        public Worker(Runnable task) {
            this.task = task;
        }

        // 重写run方法
        @Override
        public void run() {
            // 执行任务
            // 当 task 不为空, 执行任务
            // 当 task 执行完毕, 再接着从任务队列获取任务并执行
            while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
                try {
                    log.debug("正在执行...{}", task);
                    task.run();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    task = null;
                }
            }
            // 当任务队列中没有任务时, 结束并删除该线程
            synchronized (workers) {
                log.debug("worker 被移除{}", this);
                workers.remove(this);
            }

        }

    }

    // 8. 执行任务
    public void execute(Runnable task) {
        // 当任务数没有超过 coreSize 时, 直接交给 worker 对象执行
        // 当任务数超过 coreSize 时, 加入任务队列暂存
        synchronized (workers) {
            if (workers.size() < coreSize) {
                Worker worker = new Worker(task);
                log.debug("新增 worker{}, {}", worker, task);
                workers.add(worker);
                worker.start();
            } else {
                log.debug("加入任务队列 {}", task);
                taskQueue.put(task);
            }
        }

    }

}
3. 测试
@Slf4j(topic = "log.JavaTest")
public class JavaTest {
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool(2, 1000, TimeUnit.MILLISECONDS, 10);
        for (int i = 0; i < 5; i++) {
            int j = i;
            threadPool.execute(() -> {
                log.debug("{}", j);
            });
        }
    }
}
10:40:24 [main] log.ThreadPool - 新增 workerThread[Thread-0,5,main], xxx@34340fab
10:40:24 [main] log.ThreadPool - 新增 workerThread[Thread-1,5,main], xxx@725bef66
10:40:24 [main] log.ThreadPool - 加入任务队列 xxx@6e3c1e69
10:40:24 [Thread-0] log.ThreadPool - 正在执行...xxx@34340fab
10:40:24 [main] log.ThreadPool - 加入任务队列 xxx@1888ff2c
10:40:24 [main] log.ThreadPool - 加入任务队列 xxx@35851384
10:40:24 [Thread-0] log.JavaTest - 0
10:40:24 [Thread-0] log.ThreadPool - 正在执行...xxx@6e3c1e69
10:40:24 [Thread-0] log.JavaTest - 2
10:40:24 [Thread-0] log.ThreadPool - 正在执行...xxx@1888ff2c
10:40:24 [Thread-0] log.JavaTest - 3
10:40:24 [Thread-1] log.ThreadPool - 正在执行...xxx@725bef66
10:40:24 [Thread-1] log.JavaTest - 1
10:40:24 [Thread-1] log.ThreadPool - 正在执行...xxx@35851384
10:40:24 [Thread-1] log.JavaTest - 4
10:40:25 [Thread-1] log.ThreadPool - worker 被移除Thread[Thread-1,5,main]
10:40:25 [Thread-0] log.ThreadPool - worker 被移除Thread[Thread-0,5,main]

4. 线程池拒绝策略

当任务源源不断的过来,而我们的系统又处理不过来的时候,我们要采取的策略是拒绝服务。

即自定义解决方法,例如当任务队列满时,是采用阻塞添加还是带超时阻塞添加

1. 自定义拒绝策略接口
@FunctionalInterface // 拒绝策略
interface RejectPolicy<T> {
	void reject(BlockingQueue<T> queue, T task);
}
2. 自定义任务队列中改变
// 添加tryPut方法
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
    lock.lock();
    try {
        // 判断队列是否满
        if (queue.size() == capcity) {
            rejectPolicy.reject(this, task);
        } else { // 有空闲
            log.debug("加入任务队列 {}", task);
            queue.addLast(task);
            emptyWaitSet.signal();
        }
    } finally {
        lock.unlock();
    }
}
3. 自定义线程池中改变
// 添加该成员变量
private RejectPolicy<Runnable> rejectPolicy;

// 重写构造器
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapcity,
                  RejectPolicy<Runnable> rejectPolicy) {
    this.coreSize = coreSize;
    this.timeout = timeout;
    this.timeUnit = timeUnit;
    this.taskQueue = new BlockingQueue<>(queueCapcity);
    this.rejectPolicy = rejectPolicy;
}

// 重写执行任务方法
public void execute(Runnable task) {
    // 当任务数没有超过 coreSize 时, 直接交给 worker 对象执行
    // 当任务数超过 coreSize 时, 加入任务队列暂存
    synchronized (workers) {
        if (workers.size() < coreSize) {
            Worker worker = new Worker(task);
            log.debug("新增 worker{}, {}", worker, task);
            workers.add(worker);
            worker.start();
        } else {
            log.debug("加入任务队列 {}", task);
            // taskQueue.put(task);
            taskQueue.tryPut(rejectPolicy, task); // 此处改变

        }
    }

}
4. 测试
@Slf4j(topic = "log.JavaTest")
public class JavaTest {

    public static void main(String[] args) {
        
        // 1. 死等
        ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1, (queue, task) -> {
            queue.put(task);
        });
        
        // 2. 带超时等待
		ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1, (queue, task) -> {
            queue.offer(task, 1000, TimeUnit.MILLISECONDS);
        });
        
        // 3. 让调用者放弃任务执行  不做任何处理即是放弃任务执行
        ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1, (queue, task) -> {
            log.debug("放弃{}", task);
        });
        
        // 4. 让调用者抛出异常
        ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1, (queue, task) -> {
            throw new RuntimeException("任务执行失败 " + task);
        });
        
        for (int i = 0; i < 5; i++) {
            int j = i;
            threadPool.execute(() -> {
                log.debug("{}", j);
            });
        }
    }
}

5. 总结

以上即是,线程池实现的所有代码,代码比较简单,但能帮助我们很好理解线程池的基本原理,当了解这些基本原理后就可以很好的理解Java中线程池的核心代码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值