线程池的简单实现
一)、多线程的弊端
1).线程数量过大,耗尽CPU和内存资源。
2).线程的创建和关闭需要花费时间。
3).线程本身也要占用内存空间。
4).大量的线程回收会给GC带来压力,延长GC的停顿时间。
二)、线程池的作用
1).节省多线程在并发时不断创建和销毁线程所带来的额外开销,实现线程的复用。
2).使用线程池后,线程的创建和关闭由线程池维护。
三)、线程池的设计
1).TheadPool: 存储线程,维护线程的创建和关闭的线程池对象
结构:
/*
属性:
* 存放线程的集合: idleThreads, Vector类型
* 线程池的状态: isShoutdown, boolean类型
* 线程池中线程的个数: countThreads, int类型
*
* 方法:
* rePool(): 回收线程,添加至线程池中
* start(): 取出线程池中的线程,执行任务
* shutdown(): 关闭线程
*
* 注: 线程的创建和关闭都是由线程池控制的
*/
2).PreThread: 一个与线程池配合永不退出的线程。
结构:
/**
属性:
* 所属的线程池对象: ThrealPool
* 当前线程的闲置状态: isIdle , boolean类型
* 当前线程的运行状态:isShutdown, Boolean
* 线程任务对象: Runnable, target
*
* 方法:
* 构造方法:public PreThread(Runable target, String name, ThreadPool)
* target: 当前线程池里的线程要执行的任务
* name: 当前线程的名字
* ThreadPool: 当前线程的所属线程池
*
* run(): 调用线程任务
* setTarget(Runable target): 复用线程池的对象,重启设置线程任 务。
* shutdown(): 关闭当前线程
四)、简单线程池的实现
ThreadTool: 线程池
/**
* 线程池对象
* 属性:
* 存放线程的集合: idleThreads, Vector类型
* 线程池的状态: isShoutdown, boolean类型
* 线程池中线程的个数: countThreads, int类型
*
* 方法:
* rePool(): 回收线程,添加至线程池中
* start(): 取出线程池中的线程,执行任务
* shutdown(): 关闭线程
*
* 注: 线程的创建和关闭都是由线程池控制的
*/
public class ThreadPool {
/**
* 单例模式的线程池对象
*/
private static ThreadPool instance;
/**
* 存放空闲线程的集合
*/
private List<PreThread> idleThreads;
/**
* 线程池的状态
*/
private boolean isShutdown;
/**
* 线程池中线程的个数
*/
private int countThreads;
/**
* 私有化线程池构造方法,构建单例模式
*/
private ThreadPool(){
//刚开始,线程池的初始容量为5
idleThreads = new Vector<PreThread>(5);
//线程池里的线程个数为0
countThreads = 0;
}
/**
* 获取线程池实例
*/
public static synchronized ThreadPool getInstance(){
if(instance == null){
instance = new ThreadPool();
}
return instance;
}
/**
* 关闭线程池
*/
public void shutdown(){
//设置线程池的状态
isShutdown = true;
//关闭线程池里的所有线程
for(int i = 0; i < idleThreads.size(); i++){
PreThread preThread = idleThreads.get(i);
preThread.shutdown();
}
}
/**
* 添加空闲线程至线程池
*/
public void rePool(PreThread rePoolingThread){
//判断当前线程池的状态
if(!isShutdown){
//如果线程池未关闭,将线程添加进线程池中
idleThreads.add(rePoolingThread);
}
//若线程已关闭,则关闭当前线程
rePoolingThread.shutdown();
}
/**
* 开启任务线程
*/
public void start(Runnable target){
//判断线程池中是否有线程,有则复用,没有则创建
if(idleThreads.size() > 0){
//有线程则取出线程,重置线程任务
int lastIndex = idleThreads.size() - 1;
//取出线程
PreThread preThread = idleThreads.get(lastIndex);
//将该线程对象从线程池中移除
idleThreads.remove(lastIndex);
//重置线程任务,唤醒等待线程
preThread.setTarget(target);
}
//如若线程池中没有闲置线程,则重新创建线程
else{
PreThread preThread = new PreThread(target,"preThread#"+countThreads,this);
//开启任务线程,并将线程放入线程池
preThread.start();
}
}
}
PreThread: 与线程池配合的永不退出的线程对象
/**
* 与线程池配合的永不退出的线程
* 属性:
* 所属的线程池对象: ThrealPool
* 当前线程的闲置状态: isIdle , boolean类型
* 当前线程的运行状态:isShutdown, Boolean
* 线程任务对象: Runnable, target
*
* 方法:
* 构造方法:public PreThread(Runable target, String name, ThreadPool)
* target: 当前线程池里的线程要执行的任务
* name: 当前线程的名字
* ThreadPool: 当前线程的所属线程池
*
* run(): 调用线程任务
* setTarget(Runable target): 复用线程池的对象,重启设置线程任务
* shutdown(): 关闭当前线程
*/
public class PreThread extends Thread{
/**
* 线程所属线程池
*/
private ThreadPool pool;
/**
* 线程要执行的线程任务
*/
private Runnable target;
/**
* 当前线程的闲置状态
*/
private boolean isIdle = false;
/**
* 当前线程的状态
*/
private boolean isShutdown = false;
/**
* 构造方法,指定线程池,线程任务,线程名字
*/
public PreThread(Runnable target, String name, ThreadPool threadPool){
super(name);
this.target = target;
this.pool = threadPool;
}
/**
* 复用线程池的对象,重置线程任务
*/
public void setTarget(Runnable target){
this.target = target;
//唤醒等待池的线程对象,由waiting状态转为Runable状态,去抢夺系统的资源
synchronized (this) {
//执行notifyAll()后,线程自行抢夺资源,直接进入运行状态,调用run()方法,不用显示的调用start()。
notifyAll();
}
}
/**
* 关闭当前线程
*/
public void shutdown(){
//设置当前的线程为关闭状态,唤醒线程
isShutdown = true;
synchronized (this) {
notifyAll();
}
}
/**
* 执行线程任务的逻辑
*/
@Override
public void run(){
//判断当前的线程是否关闭
//线程未关闭,执行任务线程的业务逻辑
if(!isShutdown){
//设置线程的状态,非闲置状态
isIdle = false;
//执行任务线程
target.run();
//重新设置线程状态为空闲状态
isIdle = true;
//添加当前线程进入线程池
pool.rePool(this);
//执行完任务,线程进入等待状态
try {
/**
* 解释一下,这里为什么要加入代码块:
* 因为wait()/notify()/notifyAll()是配合锁对象使用的。
* 1).如果线程要调用对象的wait()方法,必须首先获得该对象的监视器 锁,调用wait()之后 当前线程又立即释放掉锁,线程随后进入 WAIT_SET(等待池)中。
* 2).如果线程要调用对象的notify()/notifyAll()方法,也必须先获得 对象的监视器锁调用方法之后,立即释放掉锁然后处于Wait_set的线 程被转移到Entry_set(等锁池)中去竞争锁资源.。The Winner Thread,也就是成功获得了对象的锁的线程,就是对象锁的拥有者,
会进入runnable状态。
*3).由于需要获得锁之后才能够调用wait()/notify()方法,因此必须将 它们放到同步代码块中
*/
synchronized (this) {
//线程进入等待状态,进入到Wait Set中,并释放锁
wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
五)、使用线程池和不使用线程池的性能比较
ThreadTask: 线程任务类
/**
* 线程任务类
*/
public class ThreadTask implements Runnable{
private String name;
public ThreadTask(String name){
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ThreadTest: 测试类
自定义线程池对象:
/**
* 比较实用线程池和不使用线程池的性能
*/
public class ThreadTest {
public static void main(String[] args) {
/**
* 不使用线程池
*/
long start0 = System.currentTimeMillis();
for(int i = 0; i < 1000; i++){
new Thread(new ThreadTask("testThread#"+i)).start();
}
System.out.println("不使用线程池所花费的时间:"+(System.currentTimeMillis() - start0));
/**
* 实用线程池
*/
//构造线程池
ThreadPool pool = ThreadPool.getInstance();
long start1 = System.currentTimeMillis();
for(int i = 0; i < 100; i++){
pool.start(new ThreadTask("testThread#"+i));
}
System.out.println("使用线程池所花费的时间:"+(System.currentTimeMillis() - start1));
}
}
结果:
不使用线程池所花费的时间:63
使用线程池所花费的时间:7
使用JDK自定义的线程对象:
/**
* 使用JDK自带的线程池:
*
*
*/
public class JDK_ThreadPool {
public static void main(String[] args) {
/**
* 不使用线程池
*/
long start0 = System.currentTimeMillis();
for(int i = 0; i < 1000; i++){
new Thread(new ThreadTask("testThread#"+i)).start();
}
System.out.println("不使用线程池所花费的时间:"+(System.currentTimeMillis() - start0));
/**
* 使用线程池
*/
//JDK自带的线程池
//ThreadPool pool = ThreadPool.getInstance();
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
long start1 = System.currentTimeMillis();
for(int i = 0; i < 100; i++){
//pool.start(new ThreadTask("testThread#"+i));
executor.execute(new ThreadTask("testThread#"+i));
}
System.out.println("使用线程池所花费的时间:"+(System.currentTimeMillis() - start1));
}
}
结果:
不使用线程池所花费的时间:62
使用线程池所花费的时间:7
结论: 不使用线程池的线程需创建1000个线程对象,使用线程池创建的线程对象小于1000个,故使用线程池的性能优于不使用线程池的性能。