〇.前言
本文适用于线程或线程池小白,或者是想了解线程池的程序猿和为面试做准备的同学。
本文知识来自于网课。
一.为什么要使用线程池
a.用多线程的目的是什么?
充分利用CPU,并发做多件事。
b.单核cpu适不适合用多线程?
适合,如果是单线程,线程中需要等待IO时,此时CPU就空闲出来了。
c.线程什么时候会让出cpu?
阻塞时 wait awaie 等待IO
sleep
yield
结束
d.线程是什么?
一条代码执行流,完成一组代码的执行。
这一组代码,我们往往成为一个任务。
e.cpu在做什么工作?
执行代码
小总结:任务(code)->被线程包装->运行到cpu上执行
f.线程是不是越多越好?
并不是!创建线程要占用很多内存。
①问:造线程要不要时间?一次性使用,用完了得销毁,销毁要不要耗时间?
答:线程在java中是一个对象,每一个java线程都需要一个操作系统线程支持。线程创建,销毁需要事件。如果创建时间+销毁时间>执行事件,就会很亏!
②问:造很多的线程,得需要空间来放它们,会不会造成内存紧张?
答:java对象占用堆内存,操作系统线程占用系统内存,根据jvm规范,一个线程默认最大栈大小1M,这个栈空间是需要系统内存中分配的。线程过多,会消费很多的内存。操作系统需要频繁切换线程上下文(大家都想被运行),影响性能。
g.该如何正确使用多线程?
多线程的目的:充分利用cpu并发做事(多做事)
线程的任务:将代码送给cpu执行 用合适数量的线程不断运送代码即可。
这合适数量的线程就构成了一个池。
有任务要执行,就放入池中,池中的一个线程把任务运送到cpu执行。
h.如何确定合适数量的线程?
1.如果是计算型任务:cpu数量的1-2倍
2.如果是IO型任务: 需要多一些线程,要根据具体的IO阻塞时长进行考量决定。比如tomcat中默认最大线程数为200。也可考虑根据需要在一个最小数量和最大数量间自动增减线程数(cpu核数20-30倍)。
注:当然具体数量请根据应用进行实测,这里只提供估值。
二.简易线程池分析
a.线程池中有什么?
仓库、任务
线程
b.线程池要做哪些事情?
接受任务放入仓库
工作线程从仓库取任务、执行
没有任务时,线程阻塞,当有任务时唤醒线程执行
c.建模
任务用什么表示Runnable
仓库用什么表示
池中线程用什么存放
如果做到工作线程的阻塞、唤醒
d.BlockingQueue介绍
阻塞队列,线程安全。
在队列为空时的获取阻塞,在队列满时放入阻塞。
BlokingQueue方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同: .第一种是抛出一个异常;第二种是返回一个特殊值(null或false具体操作取决于操作) ;第三种是在操作可以成功前,无限期地阻塞当前线程;第四种是在放弃前只在给定的最大时间限制内阻塞。详情见下表:
e.首先实现一个固定池大小的线程池理论
1.完成池的构成元素初始化。
2.完成提交任务方法。如果仓库满了就返回false。
3.完成线程中取任务、执行。
4.实现关闭功能:关闭后,不能接受新任务,待现有任务都完成后,所有线程都结束。
f.简单实现(为了贴代码美观,把所有的东西都写在了一个类里面)
package threadExercise;
import java.lang.Thread.State;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
public class MyFixedSizeThreadPool {
//仓库
private BlockingQueue<Runnable> taskQueue;
//工作线程
private List<Worker> workers;
//线程池的工作标志
private volatile boolean working = true;
public MyFixedSizeThreadPool(int poolSize , int taskQueuesSize) {
if(poolSize <= 0 || taskQueuesSize <= 0) {
throw new IllegalArgumentException("参数错误");
}
this.taskQueue = new LinkedBlockingQueue<Runnable>(taskQueuesSize);
//初始化线程
this.workers = Collections.synchronizedList(new ArrayList<>());
for(int i = 0 ; i < poolSize ; i++) {
Worker w = new Worker(this);
w.start();
this.workers.add(w);
}
}
//放入任务的方法
public boolean submit(Runnable task) {
if(this.working) {
return this.taskQueue.offer(task);
}
return false;
}
//关闭方法
public void shutdown() {
this.working = false;
//中断阻塞的线程
for(Thread t : this.workers) {
if(t.getState().equals(State.BLOCKED)
|| t.getState().equals(State.WAITING)
|| t.getState().equals(State.TIMED_WAITING)) {
t.interrupt();
}
}
}
private static class Worker extends Thread {
private MyFixedSizeThreadPool pool;
public Worker(MyFixedSizeThreadPool pool) {
super();
this.pool = pool;
}
@Override
public void run() {
//执行的任务数量计算
int taskCount = 0;
while(this.pool.working || this.pool.taskQueue.size() > 0) {
Runnable task = null;
try{
//当队列中有任务,池已经关闭时
if(this.pool.working) {
task = this.pool.taskQueue.take();
} else {
task = this.pool.taskQueue.poll();
}
} catch (Exception e) {
System.out.println("这里被中断");
}
if(task != null) {
task.run();
taskCount++;
System.out.println(Thread.currentThread().getName() + "执行完" + "\ttaskCount:" + taskCount);
}
}
Executor e = null;
System.out.println("run结束");
}
}
public static void main(String[] args) {
MyFixedSizeThreadPool pool = new MyFixedSizeThreadPool(3, 6);
for(int i = 0 ; i < 6 ; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("task begin!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("睡眠睡眠");
}
System.out.println("task end!");
}
});
}
pool.shutdown();
System.out.println("结束");
}
}
三.java并发包的线程池超级简的介
重要的类关系如下(这是一个关系):
I:Executor void execute(Runnable command);
I: ExecutorService 加入Callable、Future、关闭方法
C:ForkJoinPool 支持forkJoin框架的线程池实现