思考问题?
1、线程创建的越多越好吗?
2、场景分析:如果有一亿任务,每一个任务过来我都要创建一个线程,那代表我要总共创建一亿个线程,这样会出现什么问题?
3、线程池有什么优点?
线程池原理
先思考:
1、为什么是这种结构,还有其他结构能自定义线程池?
2、你能说出来背后的逻辑吗?讲给自己听听,自己能否听得懂自己的话?
图片逻辑分析:
main 方法创造任务,threadPool里面的线程拿到任务要执行,本来只需要这两个东西 ,一个创建,一个消费就结束了,那为什么还需要中间的阻塞队列呢?
问题的关键在于我还没创建任务,你就问我要任务,我怎么给,只能你先给我排队;你创建了这么多任务,我只有三个线程来消费你的任务,多余的我也不能处理,多余的先给我排队去,我处理完手上的工作再处理你们。
手写线程池
先思考:阻塞队列如何写呢?有哪些变量,有哪些方法供别人使用呢?
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.BlockingQueue")
class BlockingQueue<T> {
//1、任务队列 因为需要先进先出 所以用链表来实现
private Deque<T> queue = new ArrayDeque<>();
//2、需要加一把锁,因为线程池中的多个线程都需要从任务队列头部获取任务,要保证只能让一个线程获取
private ReentrantLock lock = new ReentrantLock();
/**
* 队列中能存多少个任务,需要定义,那问题来了,队列中任务满了或者没任务该怎么处理?
* 需要让线程池中的线程知道队列中没有任务,提供任务方需要让它也知道队伍已经满了
*/
//3、生产者条件
private Condition fullWait = lock.newCondition();
// 4、消费者条件变量
private Condition emptyWait = lock.newCondition();
//5、需要一个容量
private int capacity;
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
/**
* 条件变量已经有了 但我们需要具体的方法支持我们往队伍中添加任务和获取任务
*/
//6、向队伍中获取任务
public T take() {
lock.lock();
try {
while (queue.isEmpty()) {
try {
emptyWait.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
T t = queue.removeFirst();
fullWait.signal();
return t;
} finally {
lock.unlock();
}
}
// 7、向队伍添加任务
public void put(T element) {
lock.lock();
try {
while (queue.size() == capacity) {
try {
log.debug("等待加入任务队列{}",element);
fullWait.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("加入任务队列{}",element);
queue.addLast(element);
emptyWait.signal();
} finally {
lock.unlock();
}
}
// 8、获取容器大小
public int size() {
lock.lock();
try {
return queue.size();
} finally {
lock.unlock();
}
}
// 带超时的阻塞获取方法
public T poll(long time, TimeUnit unit) {
lock.lock();
try {
long nanos = unit.toNanos(time);
while (queue.isEmpty()) {
try {
if (nanos < 0) {
return null;
}
// nanos= 返回的时总时长-已使用时间
nanos = emptyWait.awaitNanos(nanos);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
T t = queue.removeFirst();
fullWait.signal();
return t;
} finally {
lock.unlock();
}
}
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
// 判断队列是否满
if(queue.size() == capacity) {
rejectPolicy.reject(this, task);
} else { // 有空闲
log.debug("加入任务队列 {}", task);
queue.addLast(task);
emptyWait.signal();
}
} finally {
lock.unlock();
}
}
/**
* 带超时时间的任务队列中添加方法
* @param task 任务
* @param timeout 超时时间
* @param timeUnit 单位
* @return
*/
public boolean offer(T task, long timeout, TimeUnit timeUnit) {
lock.lock();
try {
long nanos = timeUnit.toNanos(timeout);
while (queue.size() == capacity) {
try {
if(nanos <= 0) {
return false;
}
log.debug("等待加入任务队列 {} ...", task);
nanos = fullWait.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列 {}", task);
queue.addLast(task);
emptyWait.signal();
return true;
} finally {
lock.unlock();
}
}
}
先思考:线程池如何写呢?里面包含哪些变量呢?该有哪些方法呢?
import lombok.extern.slf4j.Slf4j;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
@Slf4j(topic = "c.ThreadPool")
public class ThreadPool {
/**
* 任务队列
*/
private BlockingQueue<Runnable> taskQueue;
/**
* 线程队列
*/
private HashSet<Worker> workers = new HashSet();
/**
* 核心线程数量
*/
private int coreSize;
private long timeOut;
private TimeUnit unit;
private RejectPolicy<Runnable> rejectPolicy;
/**
*
* @param coreSize 核心线程数量
* @param timeOut 超时时间
* @param unit 时间单位
* @param queueCapacity 任务队列大小
*/
public ThreadPool(int coreSize, long timeOut, TimeUnit unit, int queueCapacity,RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeOut = timeOut;
this.unit = unit;
this.taskQueue = new BlockingQueue<>(queueCapacity);
this.rejectPolicy=rejectPolicy;
}
/**
* 执行任务
* @param task 任务
*/
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 {
taskQueue.tryPut(rejectPolicy,task);
}
}
}
/**
* worker 来执行对应的任务
*/
class Worker extends Thread {
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
@Override
public void run() {
/**
* 任务不为空 直接执行
* 任务为空,判断任务队列中有无任务,有获取再执行,无从线程池中移除当前线程
*/
while(task !=null || (task=taskQueue.poll(timeOut,unit))!=null){
try {
log.debug("正在执行任务{}",task);
task.run();
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
task=null;
}
}
synchronized (workers) {
log.debug("worker{}正在被移除",this);
workers.remove(this);
}
}
}
}
先思考:线程池已经写完了,但是测试哪些内容呢?任务队列满了怎么测试?对主线程有什么影响,如何改进?
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
@Slf4j(topic = "c.TestPool")
public class TestPool {
public static void main(String[] args) {
/**
* 核心线程数2,1000毫秒过时,任务队列能装10个任务 总共12个任务
*/
ThreadPool threadPool = new ThreadPool(2, 1000, TimeUnit.MILLISECONDS, 10);
/**
* 模拟现在有15个任务,看线程池怎么处理
*/
for (int i = 0; i < 15; i++) {
int j=i;
threadPool.execute(()->{
try {
Thread.sleep(1000000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("{}",j);
});
}
}
}
对主线程有什么影响?(解决方案->拒绝策略)
线程测试(这里模拟了一直等待,抛出异常,不处理,让主线程执行等策略,用到了策略模式)
@Slf4j(topic = "c.TestPool")
public class TestPool {
public static void main(String[] args) {
/**
* 核心线程数1,1000毫秒过时,任务队列能装1个任务 总共2个任务
*/
ThreadPool threadPool = new ThreadPool(1, 1000,
TimeUnit.MILLISECONDS, 1,(queue,task)->{
// 1、死等
// queue.put(task);
// 2、带超时时间的添加
// queue.offer(task,1500,TimeUnit.MILLISECONDS);
// 3、放弃执行task任务
// log.debug("放弃任务{}",task);
// 4、抛异常
// throw new RuntimeException("任务执行失败"+task);
// 5、让主线程自己执行任务
task.run();
});
/**
* 模拟现在有15个任务,看线程池怎么处理
*/
for (int i = 0; i < 4; i++) {
int j=i;
threadPool.execute(()->{
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("{}",j);
});
}
}
}