您的位置:首页 > 编程语言 > Java开发

Java 线程池

2015-05-30 18:37 639 查看


Java 线程池

线程池都很多地方都会用到(例如连接数据库,下载文件等等),线程池可以让程序拥有更高的效率,但是实现线程池也需要一定的技巧,下面我们结合之前学习的线程的知识,来实现一个简单的线程池。

1.   线程池的实现原理分析

1)      原理分析:
所谓线程池,简单理解就是一个用来放线程的池子,里面放了很多很多的线程,当我们需要用到线程的时候就可以直接去线程池里面取,线程完成我们的任务之后就自动返回到线程池中。这里面就涉及到这么几个概念:
a)        线程池:它就是一个可以容纳线程的容器,里面装了很多的执行器(也就是线程),既然它容器,那么就可以有很多很多的实现方式,可以是链表(List),或者是数组(Array),只要是可以用来装的,都可以,当然,我们会选择更方便一点的容器。
b)        执行器:也就是线程,它是线程池里面管理的主要对象,我们可以向线程池获取执行器,每一个执行器都可以完成一个任务(Task),当人物完成后,就要将执行器自身放回线程池中。
c)        任务(Task):它就是我们需要提交给执行器执行的事务(就是要让线程执行的事)。
在这里大致的给出一个结构图:



以上就是线程池的大致结构,不过还需要实现对池的线程池的调度。

2.   线程池的实现步骤

1)      线程池(ThreadPool)的实现步骤
a)        创建执行器列表,当作存放执行器(线程)的容器;
b)        根据线程池的大小创建执行器并添加到执行器列表(容器)中;
c)        如果需要获取线程池中的执行器(getExecuteor()),则返回一个空闲线程列表中的执行器。
2)      执行器(Executor)实现的步骤
a)        继承自线程类,并实现前面结构图中的Executor接口;
b)        创建线程锁lock,在run()方法中等待(wait)资源或任务(Task);
c)        在设置完任务(setTask)后,就唤醒自己(lock.notify()),开始执行任务(startTask);
d)        完成任务后,将自己添加回线程池(注意使用线程池pool的同步锁同步,因为pool共享资源),并唤醒线程池中的其其它正在等待分配线程的任务Executor(pool.notifyall)。
3)      任务(Task)的实现步骤(当然,任务是可以自定义的哦)
a)        实现Task接口,将要执行的任务卸载execute()方法中。
 
原理什么的分析完毕之后,就要开始写代码实现了,下面我们一一来实现线程池、执行器和任务最后再写一个测试类来测试一下我们的线程池到底有没有效果:
首先要定义好我们的三个接口Pool、Exectutor、Task:
Pool接口:

package learn.threadPool;

/** 池接口*/
public interface Pool {
/** 获取执行器 */
public Executor getExecutor();

/** 销毁线程池 */
public void destory();
}


Exectutor接口:

package learn.threadPool;

/** 执行器接口*/
public interface Executor {
/** 设置任务 */
public void setTask(Task task);

/** 开始执行任务并唤醒自己 */
public void startTask();

/** 获取正在执行的任务 */
public Task getTask();
}


 
Task接口:

package learn.threadPool;

/** 任务接口*/
public interface Task {
/** 执行方法,要被线程运行的代码都写在这个方法里 */
public void execute();

/** 任务完成后要做的 */
public void onResult();
}


Pool实现类:

package learn.threadPool;

import java.util.LinkedList;

/** 线程池,实现了池接口 */
public class ThreadPool implements Pool {
/** 线程池是否被关闭 */
private boolean isShut;
/** 线程列表 */
private LinkedList<Executor> pool;
/** 线程池大小 */
private int poolSize;

/** 执行器实现类,这里我们将执行器定义为一个私有成员类,方便我们对它的管理 */
private class ExecutorImpl extends Thread implements Executor {
private Task task;
/* 这里需要一个线程锁,用来防止防止同时有多个线程执行startTask()方法,并且在设置了任务后唤醒自己 */
private Object lock = new Object();

@Override
public Task getTask() {
return task;
}

@Override
public void setTask(Task task) {
this.task = task;
}

@Override
public void startTask() {
synchronized (lock) {
lock.notify();
}
}

/** 这里覆盖Thread的类的run()方法真正的去执行任务 */
@Override
public void run() {
/* 判断线程池是否已被关闭,线程池中的线城市不会结束的,只要线程池没有被关闭就要一直等待任务或执行任务 */
while (!isShut) {
try {
synchronized (lock) {
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
/* 判断任务时候为空并真正的去执行任务 */
if (task != null) {
/* 真正执行耗时任务 */
task.execute();
/* 执行任务完成后要做的事 */
task.onResult();
}
/* 执行完任务之后要把自己放回线程池中,并唤醒线程池中在等待获取执行器的线程 */
synchronized (pool) {
/* 把自己放到线程池队首 */
pool.addFirst(ExecutorImpl.this);
/* 唤醒线程池中在等待获取执行器的线程 */
pool.notifyAll();
}
}
}

}

/** 线程池构造器 */
public ThreadPool(int poolSize) {
/* 线程最小数量设为5 */
if (poolSize <= 5) {
this.poolSize = 5;
} else {
this.poolSize = poolSize;
}
pool = newLinkedList<Executor>();
/* 根据线程池大小循环网池中加入执行器 */
for (int i = 0; i < this.poolSize; i++) {
Executor executor = new ExecutorImpl();
/* 将执行器添加到池中 */
pool.add(executor);
/* 调用Thread类的start()方法开启线程 */
((ExecutorImpl) executor).start();
}
}

@Override
public Executor getExecutor() {
Executor executor = null;
/* 使用pool同步,防止线程安全问题 */
synchronized (pool) {
/* 如果线程池中还有空闲的执行器,则返回列表中的第一个执行器 */
if (pool.size() > 0) {
/* 在这里,每次都获取列表中的第一个执行器 */
executor = pool.removeFirst();
}
/* 线程池中没有空闲的执行器,则等待其它的线程释放 */
else {
try {
/* 要获取执行的线程在这里等待,知道其它的线程释放了空闲的执行器,即pool.notifyAll() */
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return executor;
}

@Override
public void destory() {
/* 防止多个线程同时销毁,造成线程安全问题 */
synchronized (pool) {
isShut = true;
/* 通知所有线程池中的执行器:线程池已被销毁 */
pool.notifyAll();
/* 清空线程池的线程 */
pool.clear();
}
}

}


 
我们的测试用的任务:

package learn.threadPool;

/** 我们的下载任务 */
public class DownloadTask implements Task {

private String netFilePath;
private String savedFilePath;
private int startIndex;
private int endIndex;

public DownloadTask(String netFilePath, String savedFilePath,
int startIndex, int endIndex) {
this.netFilePath = netFilePath;
this.savedFilePath = savedFilePath;
this.startIndex = startIndex;
this.endIndex = endIndex;
}

@Override
public void execute() {
download();
}

@Override
public void onResult() {
System.out.println(savedFilePath + "文件的第 " + startIndex + " --> "
+ endIndex + " 字节部分已下载完毕");
}

private void download() {
try {
/* 让线程睡眠一定时间,模拟网络下载 */
long delay = 2000;
Thread.sleep(delay);
/* 打印下载完毕 */
System.out.println("网络路径:" + netFilePath);

} catch (Exception e) {
e.printStackTrace();
}
}
}


测试类
 

package learn.threadPool;
/** 测试一下我们的线程池 */
public class ThreadPoolTest  {

public void tryDownload() {
String netFilePath = "http://dldir1.qq.com/qqfile/qq/QQ7.2/14810/QQ7.2.exe";
String savedFilePath = "E:\\下载\\QQ.exe";
/* 创建一个6个线程的线程池 */
ThreadPool threadPool = new ThreadPool(6);
int notFinishNum = 5;
for (int i = 0; i < notFinishNum; i++) {
/* 添加任务 */
Task task = new DownloadTask(netFilePath, savedFilePath, i * 1000,
i * 1000 + 1000);
Executor executor = threadPool.getExecutor();
executor.setTask(task);
executor.startTask();
}

}

/** 测试 */
public static void main(String[] args) {
newThreadPoolTest().tryDownload();
}

}


程序输出:

网络路径:http://dldir1.qq.com/qqfile/qq/QQ7.2/14810/QQ7.2.exe
网络路径:http://dldir1.qq.com/qqfile/qq/QQ7.2/14810/QQ7.2.exe
网络路径:http://dldir1.qq.com/qqfile/qq/QQ7.2/14810/QQ7.2.exe
网络路径:http://dldir1.qq.com/qqfile/qq/QQ7.2/14810/QQ7.2.exe
网络路径:http://dldir1.qq.com/qqfile/qq/QQ7.2/14810/QQ7.2.exe
E:\下载\QQ.exe文件的第 4000 --> 5000 字节部分已下载完毕
E:\下载\QQ.exe文件的第 2000 --> 3000 字节部分已下载完毕
E:\下载\QQ.exe文件的第 1000 --> 2000 字节部分已下载完毕
E:\下载\QQ.exe文件的第 3000 --> 4000 字节部分已下载完毕
E:\下载\QQ.exe文件的第 0 --> 1000 字节部分已下载完毕


 
实际上,这些并不全都需要我们自己去完成,在java.util.concurrent包中,Java也已经为我们封装好了一些工具类,这里只是作为学习,所以全都自己写可以自己多看看java.util.concurrent包中API,加强学习哦。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 编程语言