上一篇看这里:JAVA并发编程-7-并发容器
1、降低资源的消耗。降低线程创建和销毁的资源消耗;
2、提高响应速度:线程的创建时间为T1,执行时间T2,销毁时间T3,免去T1和T3的时间
3、提高线程的可管理性。
如何实现线程呢?有2个关键点
1、线程必须在池子已经创建好了,并且可以保持住,要有容器保存多个线程;
2、线程还要能够接受外部的任务,运行这个任务。容器保持这个来不及运行的任务.
/**
* 类说明:自己线程池的实现
*/
public class MyThreadPool2 {
// 线程池中默认线程的个数为5
private static int WORK_NUM = 5;
// 队列默认任务个数为100
private static int TASK_COUNT = 100;
// 工作线程组
private WorkThread[] workThreads;
// 任务队列,作为一个缓冲
private final BlockingQueue<Runnable> taskQueue;
private final int worker_num;//用户在构造这个池,希望的启动的线程数
// 创建具有默认线程个数的线程池
public MyThreadPool2() {
this(WORK_NUM, TASK_COUNT);
}
// 创建线程池,worker_num为线程池中工/作线程的个数
public MyThreadPool2(int worker_num, int taskCount) {
if (worker_num <= 0) worker_num = WORK_NUM;
if (taskCount <= 0) taskCount = TASK_COUNT;
this.worker_num = worker_num;
taskQueue = new ArrayBlockingQueue<>(taskCount);
workThreads = new WorkThread[worker_num];
for (int i = 0; i < worker_num; i++) {
workThreads[i] = new WorkThread();
workThreads[i].start();
}
}
// 执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器决定
public void execute(Runnable task) {
try {
taskQueue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁
public void destroy() {
// 工作线程停止工作,且置为null
System.out.println("ready close pool.....");
for (int i = 0; i < worker_num; i++) {
workThreads[i].stopWorker();
workThreads[i] = null;//help gc
}
taskQueue.clear();// 清空任务队列
}
// 覆盖toString方法,返回线程池信息:工作线程个数和已完成任务个数
@Override
public String toString() {
return "WorkThread number:" + worker_num
+ " wait task number:" + taskQueue.size();
}
/**
* 内部类,工作线程
*/
private class WorkThread extends Thread {
@Override
public void run() {
Runnable r = null;
try {
while (!isInterrupted()) {
r = taskQueue.take();
if (r != null) {
System.out.println(getId() + " ready exec :" + r);
r.run();
}
r = null;//help gc;
}
} catch (Exception e) {
// TODO: handle exception
}
}
public void stopWorker() {
interrupt();
}
}
}
上面类中,在构造方法中,定义好线程容器,并且在其中创建指定数量的线程,定义了任务队列来存放任务。
execute方法就是向任务队列中放入需要执行的任务。
定义了WorkThread作为单个工作线程,它不断的从taskQueue任务队列中去取得任务,然后去运行它。
public class TestMyThreadPool {
public static void main(String[] args) throws InterruptedException {
// 创建3个线程的线程池
MyThreadPool2 t = new MyThreadPool2(3,0);
t.execute(new MyTask("testA"));
t.execute(new MyTask("testB"));
t.execute(new MyTask("testC"));
t.execute(new MyTask("testD"));
t.execute(new MyTask("testE"));
System.out.println(t);
Thread.sleep(10000);
t.destroy();// 所有线程都执行完成才destory
System.out.println(t);
}
// 任务类
static class MyTask implements Runnable {
private String name;
private Random r = new Random();
public MyTask(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void run() {// 执行任务
try {
Thread.sleep(r.nextInt(1000)+2000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getId()+" sleep InterruptedException:"
+Thread.currentThread().isInterrupted());
}
System.out.println("任务 " + name + " 完成");
}
}
}
我们自己实现的线程池还有很多缺点:
1,线程池在创建时,就创建启动好了线程,如果任务数量小于线程数的时候,会导致线程无意义的等待。我们不能很好的控制线程的数量
2,阻塞队列满了的时候,queue只能被阻塞,线程池应用速度变慢,没有一个有效的机制来处理这种情况
线程池的创建主要依赖ThreadPoolExecutor类,它是jdk中所有线程池的父类。它的构造参数如下:
AbortPolicy :直接抛出异常,默认;
CallerRunsPolicy:用调用者所在的线程来执行任务
DiscardOldestPolicy:丢弃阻塞队列里最老的任务,队列里最靠前的任务
DiscardPolicy :当前任务直接丢弃
实现自己的饱和策略,实现RejectedExecutionHandler接口即可
execute(Runnable command) 不需要返回值
Future submit(Callable task) 需要返回值
shutdownNow():设置线程池的状态,还会尝试停止正在运行或者暂停任务的线程
shutdown()设置线程池的状态,只会中断所有没有执行任务的线程
来看一段重要的源码:
如果小于核心线程数,就直接增加任务。否则就尝试将任务放到等待队列中,如果放失败了,就在小于最大线程数的条件下进行新线程创建,如果仍然没有成功,就拒绝任务。
线程池的使用也有它的合理性,并不是线程越多就越好,需要根据不同类型的任务来合理的配置线程池。
根据任务的性质来:
计算密集型(CPU),IO密集型,混合型
队列的选择上,应该使用有界,无界队列可能会导致内存溢出,OOM
jdk中给我们预定义了一些线程池,来方便特殊情况下的使用。
Executor的继承关系大体如下:
Executor框架的基本使用流程:
下一篇:JAVA并发编程-9-并发安全