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

JAVA多线程之——线程池

2017-04-06 21:42 162 查看

线程池

线程池顾名思义,就是一个放置线程的池子。就跟数据库连接池差不多。线程池通过对并发线程的控制,能有效的节省系统资源的浪费,提高系统的性能。

学习线程池,先了解一下线程池的一个基本结构:



Executor

public interface Executor {
void execute(Runnable command);
}


Executor是一个接口,其中只有一个方法,就是execute方法。所以Executor实际就是一个线程的执行者。

这里就不把子类的所有方法全部列出来,全部学习一遍,大体可以参照API进行学习。这里主要学习线程池ThreadPoolExecutor方法开始学习,来了解学习线程池的一个运行原理。

ThreadPoolExecutor的一些重要属性

//ctl用来表示线程池的运行状态,和线程池中任务的数量。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//进制位。最大整数转换成二进制的位数
private static final int COUNT_BITS = Integer.SIZE - 3;
//线程池的最大容量2的29次方减1
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

。
//运行状态,能接收任务,并对已经添加的任务进行处理
private static final int RUNNING    = -1 << COUNT_BITS;
//不能接收新任务,但能处理已经添加的任务
private static final int SHUTDOWN   =  0 << COUNT_BITS;
//不能接收新任务,也不能处理已经添加的任务,并且中断已经在处理的任务
private static final int STOP       =  1 << COUNT_BITS;
//当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
private static final int TIDYING    =  2 << COUNT_BITS;
//线程池彻底终止,就变成TERMINATED状态。
private static final int TERMINATED =  3 << COUNT_BITS;

// Packing and unpacking ctl
//返回线程池的状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
//返回线程池的有效容量
private static int workerCountOf(int c)  { return c & CAPACITY; }
//初始化ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
//线程的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//互斥锁
private final ReentrantLock mainLock = new ReentrantLock();
//线程工作集
private final HashSet<Worker> workers = new HashSet<Worker>();
//终止条件
private final Condition termination = mainLock.newCondition();
// 线程池中线程数量曾经达到过的最大值。
private int largestPoolSize;
// 已完成任务数量
private long completedTaskCount;
//线程工厂
private volatile ThreadFactory threadFactory;
//线程被拒绝时的处理策略
private volatile RejectedExecutionHandler handler;
// 保持线程存活时间。
private volatile long keepAliveTime;
//是否允许"线程在空闲状态时,仍然能够存活
private volatile boolean allowCoreThreadTimeOut;
//核心线程池大小
private volatile int corePoolSize;
//线程池的最大容量
private volatile int maximumPoolSize;


这么多变量,可能要记住也不是很容易。通过分析Execute方法可以更加好的理解工作原理和这些属性的意义

public void execute(Runnable command) {
//执行的线程为空抛出空指针异常。
if (command == null)
throw
d9f6
new NullPointerException();
//这里整体可以分三步。
//1. 如果当前线程池中任务数量(一个任务可以理解就是一个线程)小于中心池的容量(corePoolSize),就直接为command新建一个线程,加入任务集workers,并启动该线程。

int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//2.如果当前任务数量大于了corePoolSize,并且线程池是可运行状态。就把任务加入到任务队列workQueue中。 加入队列之后,再次确认线程池的状态,这个时候状态不是可运行的,那就把任务从队列中删除,并尝试终止线程池。如果是可运行,那么就检查线程池中工作数量是否为0,如果没有了,那么就添加一个任务为空的线程。
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//3.如果任务数量大于中心池数量,添加对了也失败了(这里队列是BlockingQueue,前面学习过队列有有界队列和无界队列,所以有可能队列满了导致添加失败。活着其它原因),那么就再进行一次尝试添加到任务集中去,如果失败,执行拒绝策略。
else if (!addWorker(command, false))
reject(command);
}


通过上面的步骤学习,可以大致的理一下思路,线程池,有一个中心池容量,这个容量没有满,就可以直接添加任务运行,而任务是被 添加到一个HashSet的Worker中。如果满了,就把任务添加到一个BlockingQueue队列中。都失败了,就直接运行一个拒绝策略。所以,就要理解三个东西:

工作集。

任务队列

拒绝策略。

理解了这三个东西,那么大致就可以了解线程池的一个基本原理。

工作集 Worker

//Worker是线程池的一个内部类 集成了AQS,实现了Runnable接口。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{

private static final long serialVersionUID = 6138294804551838833L;

final Thread thread;

Runnable firstTask;

volatile long completedTasks;

Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker 运行前,不准中断
this.firstTask = firstTask; //初始任务值
this.thread = getThreadFactory().newThread(this);//通过线程工为当前任务创建一个线程
}

public void run() {
runWorker(this);//运行
}
protected boolean isHeldExclusively() {
return getState() != 0;
}

protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}

protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}

public void lock()        { acquire(1); }
public boolean tryLock()  { return tryAcquire(1); }
public void unlock()      { release(1); }
public boolean isLocked() { return isHeldExclusively(); }

void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}


工作集的就是把任务通过线程工厂创建一个该任务的线程并运行。

* 任务队列*

从定义上看我们知道任务队列是一个BlockingQueue。所以线程池中的任务队列可以是任意BlockingQueue的子类。但是常用线程池中常用的的是ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue .下一节会学习重用的线程池类型。

拒绝策略

AbortPolicy – 当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常。

CallerRunsPolicy – 当任务添加到线程池中被拒绝时,会在线程池当前正在运行的Thread线程池中处理被拒绝的任务。

DiscardOldestPolicy – 当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。

DiscardPolicy – 当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务。

大致了解学习了线程池的一个主要运行过程和基本原理。下一节将会学习JDK自带的几种线程池,更加进一步学习和理解线程池。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 线程池