无限制线程的缺陷
为了提高对多核处理器使用,多线程的操作成了不可替代的利器。但不对线程加以有效管理,线程的创建及销毁都会消耗不必要的性能。
简单的线程池实现
为了在使用过程中,对线程进行一个有效的管理,就引入了线程池的概念–进行线程的复用。
public class ThreadPool {
private static ThreadPool instance = null;
/**
* 空闲线程队列
*/
private List<PThread> idleThreads;
/**
* 已有的线程总数
*/
private int threadCounter;
private boolean isShutDown = false;
public ThreadPool() {
this.idleThreads = new Vector<>(5);
threadCounter = 0;
}
public int getThreadCounter() {
return threadCounter;
}
public void setThreadCounter(int threadCounter) {
this.threadCounter = threadCounter;
}
/**
* 取得线程池实例
* @return
*/
public synchronized static ThreadPool getInstance() {
if (instance == null) {
instance = new ThreadPool();
}
return instance;
}
/**
* 将线程放入线程池
* @param p
*/
protected synchronized void repool(PThread p) {
if (!isShutDown) {
idleThreads.add(p);
} else {
p.shutDown();
}
}
/**
* 停止池中所有线程
*/
public synchronized void shutDown() {
isShutDown = true;
for (int threadIndex = 0; threadIndex < idleThreads.size(); threadIndex++) {
PThread pThread = idleThreads.get(threadIndex);
pThread.shutDown();
}
}
/**
* 执行任务
* @param target
*/
public synchronized void start(Runnable target) {
PThread thread = null;
/**
* 如果直接有空闲的线程,则直接使用
*/
if (idleThreads.size() > 0) {
int lastIndex = idleThreads.size() - 1;
thread = idleThreads.get(lastIndex);
idleThreads.remove(lastIndex);
thread.setTarget(target);
} else {
threadCounter++;
thread = new PThread(target, "PThread #" + threadCounter, this);
thread.start();
}
}
}
public class PThread extends Thread {
private ThreadPool pool;
private Runnable target;
private boolean isShutDown = false;
private boolean isIdle = false;
public PThread(Runnable target, String name, ThreadPool pool) {
super(name);
this.pool = pool;
this.target = target;
}
public Runnable getTarget() {
return target;
}
public boolean isIdle() {
return isIdle;
}
@Override
public void run() {
super.run();
while (!isShutDown) {
isIdle = false;
if (target != null) {
target.run();
}
isIdle = true;
try {
pool.repool(this);
synchronized (this) {
wait();
}
} catch (InterruptedException e) {
}
isIdle = false;
}
}
public synchronized void setTarget(Runnable newTarget) {
target = newTarget;
/**
* 设置任务之后,通知run方法,开始执行这个任务
*/
notifyAll();
}
/**
* 关闭线程
*/
public synchronized void shutDown() {
isShutDown = true;
notifyAll();
}
}
public class MyThread implements Runnable {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
int size = 10000;
long start1 = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
new Thread(new MyThread("noThreadPool" + i)).start();
}
System.out.println(System.currentTimeMillis() - start1);
long start2 = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
ThreadPool.getInstance().start(new MyThread("noThreadPool" + i));
}
System.out.println(System.currentTimeMillis() - start2);
}
}
Executor框架
Executor框架是由jdk提供,在java.util.concurrent包中,
下面是常用的Executors工厂类的主要方法:
- newFixedThreadPool()方法:该方法返回一个固定的线程数量的线程池,该线程池中的线程数量始终不变。当有一个新任务提交时,线程池中若有空闲则直接执行;若没有,则新任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
- newSingleThreadExecutor()方法:该方法只产生一个线程的线程池。若有多余任务,被提交到队列中,待线程空闲,按照先入先出的顺序执行队列中的任务。
- newCachedThreadPool()方法:该方法返回一个可以根据实际情况调整的线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则优先使用可以复用的线程。若所有线程均在工作,又有新任务提交,则创建新线程处理任务。
- newSingleThreadScheduledExecutor():该方法返回一个ScheduledExecutorService对象,线程池大小为1。ScheduledExecutorService接口在ExecutorService扩展了在给定的时间执行某个任务的功能。
- newScheduledThreadPool()方法:该方法也返回ScheduledExecutorService对象,但可以指定线程池数量。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
自定义线程池
由上面的源码发现,其实线程池实现主要使用了ThreadPoolExecutor类,其构造函数如下:
/**
* @param corePoolSize 线程池中的线程数量
* @param maximumPoolSize 线程池中最大线程数量
* @param keepAliveTime 当线程池数量超过corePoolSize时,多余的空闲
* 线程的存活时间。即,超过corePoolSize的空闲
* 线程,在多长时间内会被销毁
* @param unit keepAliveTime的单位
* @param workQueue 任务队列,被提交但尚未被执行的任务
* @param threadFactory 线程工厂,用于创建线程,一般用默认的即可
* @param handler 拒绝策略。当任务处理不过来时,如何拒绝任务
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- 参数workQueue 值被提交但未执行的任务队列,他是一个BlockQueue接口的对象,仅用于Runnable接口中。根据功能分类,在ThreadPoolExecutor的构造函数中,有以下几种Blockingqueue:
- 直接提交的队列:该功能由SynchronousQueue对象提供。SynchronousQueue是一个特殊的BlockingQueue。SynchronousQueue没有容量,每一个插入操作都要等待一个相应的删除操作。SynchronousQueue也不保存任务,它总是把任务提交给线程执行,如果没有空闲的线程,则创建新的线程,如果线程数量已经达到最大值,则执行拒绝策略。
- 有界的任务队列:有界的任务队列可以使用ArrayBlockingQueue实现。ArrayBlockingQueue的构造函数必须带一个容量参数,表示该队列的最大容量。当队列已满(也创建了最大的线程数),则执行拒绝策略。有界队列当且仅当任务队列满了,才开始创建新线程,直到等于maxumumPoolSize;
- 无界的任务队列:可以通过LinkedBlockingQueue类实现。无界队列可以无限增长,直到耗尽系统内存为止。
- 优先任务队列:优先任务队列是带有执行优先级的队列,它通过PriorityBlockingQueue实现。可以控制任务的执行先后顺序,是一个特殊的无界队列,无论有界队列ArrayBlockingQueue,还是未指定大小的无界队列LinkedBlockingQueue都是按照按照先进先出算法处理任务的。而PriorityBlockingQueue可以根据任务自身的优先级顺序先后执行,在确保系统性能的同时,也能有很好的质量保证。
- 参数handler指定了拒绝策略,即当任务数量超过系统实际承载能力时,该如何处理。Jdk内置了4中拒绝策略,如下图:
- AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作。
- CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中,运行前被丢弃
- DiscardOledestPolicy策略:该策略将丢弃最老的一个请求,也就是即将被执行的一个恩物,并尝试再次提交当前任务。
- DiscardPolicy策略:该策略默默丢弃无法处理的任务,不予任务处理。
- 自定义策略,只需继承RejectedExecutionHandler
优化线程池大小
《Java》:
Ncpu = CPU的数量
Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1
W/C = 等待时间与计算时间的比率
为保持处理器达到期望的使用率,最优的池的大小等于:
Nthreads = Ncpu x Ucpu x (1 + W/C)
你可以使用Runtime来获得CPU的数目:
int N_CPUS = Runtime.getRuntime().availableProcessors();
扩展ThreadPoolExecutor
ThreadPoolExecutor也是一个可扩展的线程池,它提供了beforeExecute()、afterExecute()和terminated()3个接口对线程池进行控制。如下为示例代码:
public class MyThreadPoolExecutor extends ThreadPoolExecutor {
public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println("beforeExecute MyThreadPoolExecutor Name " + ((Thread) r).getName() + " TID :" + t.getId());
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
System.out.println("afterExecute MyThreadPoolExecutor Name " + ((Thread) r).getName() + " TID :" + Thread.currentThread().getId());
System.out.println("afterExecute PoolSize = " + this.getPoolSize());
}
}