线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。风险与机遇用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,诸如同步错误和死锁,它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足和线程泄漏。
创建线程池以及使用:
public class ThreadPoolDemo {
public static void main(String[] args) {
LinkedBlockingQueue<Runnable> objects = new LinkedBlockingQueue<>(20);
//jdk提供了创建线程池的接口
ThreadPoolExecutor threadPoolExecutor= new ThreadPoolExecutor(10,20,3L,
TimeUnit.SECONDS,objects);
threadPoolExecutor.prestartAllCoreThreads(); // 启动线线程池中的线程
for(int i =0;i<100;i++){
// 构建线程池
threadPoolExecutor.submit(
()->{
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
);
}
}
}
Future与Callable、FutureTask
Callable与Runable功能相似,Callable的call有返回值,可以返回给客户端,而Runable没有返回值,一般情况下,Callable与FutureTask一起使用,或者通过线程池的submit方法返回相应的Future,Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果、设置结果操作。get方法会阻塞,直到任务返回结果FutureTask则是一RunnableFuture,而RunnableFuture实现了Runnbale又实现了Futrue这两个接口。
/**
* callable是runable的一个补充,实现一个带返回值的功能
*/
public class CallableDemo implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000L);
return "aaa";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableDemo callableDemo = new CallableDemo(); //无法直接使用,需要用到futuretask
FutureTask<String> futureTask = new FutureTask<String>(callableDemo);
new Thread(futureTask).start();
System.out.println(futureTask.get());// 获取这个call方法的返回值
}
}
线程池的核心组成部分及其运行机制:重点
corePoolSize:核心线程池大小 cSize
maximumPoolSize:线程池最大容量 mSize
keepAliveTime:当线程数量大于核心时,多余的空闲线程在终止之前等待新任务的最大时间。
unit:时间单位
workQueue:工作队列 nWorks
ThreadFactory:线程工厂
handler:拒绝策略
运行机制:
通过new创建线程池时,除非调用prestartAllCoreThreads方法初始化核心线程,否则此时线程池中有0个线程,即使工作队列中存在多个任务,同样不会执行
任务数X
x <= cSize 只启动x个线程
x >= cSize && x < nWorks + cSize (工作队列还没有满) 会启动 <= cSize 个线程 其他的任务就放到工作队列里
x > cSize && x > nWorks + cSize (工作队列也满了)
x-(nWorks) <= mSize (去掉工作队列中的后剩余的线程小于最大线程数) 会启动x-(nWorks)个线程
x-(nWorks) > mSize 会启动mSize个线程来执行任务,其余的执行相应的拒绝策略
线程池的拒绝策略:

拒绝的处理器接口有四个实现类:
AbortPolicy:该策略直接抛出异常,阻止系统正常工作
CallerRunsPolicy:只要线程池没有关闭,该策略直接在调用者线程中,执行当前被丢弃的任务(叫老板帮你干活)
DiscardPolicy:直接啥事都不干,直接把任务丢弃
DiscardOldestPolicy:丢弃最老的一个请求(任务队列里面的第一个),再尝试提交任务
通过将以上拒绝策略的实现类通过new 传入线程池作为参数
Executor框架: 用来创建线程池
public class ExcuteDemo {
public static void main(String[] args) {
// 通过不同的方式创建线程池
//创建一个可以根据需要创建新线程的线程池,如果有空闲线程,优先使用空闲的线程
ExecutorService executorService = Executors.newCachedThreadPool();
//传入的参数是线程数量 newFixedThreadPool:创建一个固定大小的线程池,在任何时候,最多只有N个线程在处理任务
ExecutorService executorService1 = Executors.newFixedThreadPool(10);
// 传入核心线程数量 能延迟执行、定时执行的线程池
ScheduledExecutorService scheduledExecutorService =
Executors.newScheduledThreadPool(10);
// 工作窃取,使用多个队列来减少竞争
ExecutorService executorService2 = Executors.newWorkStealingPool();
//:单一线程的线程次,只会使用唯一一个线程来执行任务,即使提交再多的任务,也都是会放到等待队列里进行等待
ExecutorService executorService3 = Executors.newSingleThreadExecutor();
// 单线程能延迟执行、定时执行的线程池
ScheduledExecutorService scheduledExecutorService1 =
Executors.newSingleThreadScheduledExecutor();
// 线程池的使用
executorService.submit(
()->{
System.out.println(Thread.currentThread().getName());
}
);
}
}
尽量避免使用Executor框架创建线程池
newFixedThreadPool newSingleThreadExecutor 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 newCachedThreadPool newScheduledThreadPool 允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM为什么第二个例子,在限定了堆的内存之后,还会把整个电脑的内存撑爆创建线程时用的内存并不是我们制定jvm堆内存,而是系统的剩余内存。(电脑内存-系统其它程序占用的内存-已预留的jvm内存)创建线程池时,核心线程数不要过大相应的逻辑,发生异常时要处理submit 如果发生异常,不会立即抛出,而是在get的时候,再抛出异常execute 直接抛出异常;
总结笔记:
https://blog.csdn.net/oneby1314/article/details/107978173
1129

被折叠的 条评论
为什么被折叠?



