1、线程池的作用
2、Java中的线程池
3、ThreadPoolExecutor的参数
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ExecutorTest {
public static void main(String[] args) {
ExecutorService esv = new ThreadPoolExecutor(2, 10, 0,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {
private AtomicInteger count = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
// TODO Auto-generated method stub
return new Thread(r, "LinkedBlockingQueue " + count.incrementAndGet());
}
});
for (int i = 0; i < 1000; i++) {
esv.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("abc");
}
});
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
当采用有界队列的时候,配合maximumPoolSize一起使用,可以有效的防止线程池的过度膨胀。当新的任务到来时,如果当前的工作线程数 <
corePoolSize的时候,创建工作线程用于处理任务。否则将任务添加到队列中,如果添加任务失败则创建新的工作线程,用于处理新的任务,如果添
加成功,则使用已有的工作线程处理这个任务。所能创建的最大工作线程数是maxinumPoolSize,而超过corePoolSize的工作线程,会采用上面
keepAliveTime策略进行删除。因此在这种情况下,线程池的执行效率受有界队列的大小,maxinumPoolSize和corePoolSize的影响,当
maxinumPoolSize和corePoolSize设置的较大的时候,会导致线程数目增多,导致CPU使用率上升,资源利用率增高,以及频繁的上线文切换。但是
maxinumPoolSize和corePoolSize设置的较小的时候,会导致任务积压在队列中,无法得到及时的执行,导致系统吞吐量的降低。因此需要平衡线程池
大小(maxinumPoolSize和corePoolSize)和队列大小之间的关系,在保证吞吐量的同时,保证资源利用率。
例子:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ExecutorTest {
public static void main(String[] args) {
ExecutorService esv = new ThreadPoolExecutor(2, 10, 1,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2), new ThreadFactory() {
private AtomicInteger count = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
// TODO Auto-generated method stub
return new Thread(r, "ArrayBlockingQueue " + count.incrementAndGet());
}
});
for (int i = 0; i < 1000; i++) {
esv.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("abc");
}
});
try {
Thread.currentThread().sleep(250);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
添加任务的速度是250ms,线程执行的时间是2000ms(2s),也就是差不多会同时存在2000ms / 250ms = 8个工作线程同时在工作。
使用jconsole查看程序的线程状况,
以ArrayBlockingQueue开头的线程有9个,和上面预估的8个线程是十分类似的。也就是说初始设置的核心线程数2,以及队列大小2,无法满足任务的执行,导致队列达到满负荷,然后导致了工作线程数的增加到了9。
当所有任务都执行完成后,查看线程状况,
工作线程数再次回到了初始的2,这是因为设置了keepAliveTime,多出来的核心线程在空闲了一定时间后,会自动销毁。
那么扩大队列的大小,会出现什么情况呢?
可以看到即使任务产生的速率比较快,但是队列足够大,能够将任务缓存下来的话,线程池不会扩大核心线程数的数目。
继续观察,当缓存队列达到最大负荷的时候,会产生新的线程。
所以如何设置线程池大小和缓存队列大小会影响着线程池运行的性能。
3)直接提交队列 SynChronousQueue
当把一个任务加入到SynChronousQueue中时,只有当某个工作线程将这个任务取走,下一个任务才能成功添加到队列中,否则就会触发添加队列失
败。出现添加失败的时候,会生成新的核心线程。这个策略和有界队列是相同的。
例子:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ExecutorTest {
public static void main(String[] args) {
ExecutorService esv = new ThreadPoolExecutor(2, 10, 1,
TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactory() {
private AtomicInteger count = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
// TODO Auto-generated method stub
return new Thread(r, "SynchronousQueue " + count.incrementAndGet());
}
});
for (int i = 0; i < 1000; i++) {
esv.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("abc");
}
});
try {
Thread.currentThread().sleep(250);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
线程状态如下,
和有界队列相类似。
4、ThreadPoolExecutor的启动和接受任务
<span style="font-size:14px;"> public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// 异常情况1
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
// 异常情况2
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
// 赋值
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}</span>
任务的执行,
<span style="font-size:14px;"> public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
// 获取控制字段
int c = ctl.get();
// 当前核心线程数 < corePoolSize
if (workerCountOf(c) < corePoolSize) {
// 添加新的线程成功,并且将command作为这个线程首先执行的任务
if (addWorker(command, true))
return;
// 重新获取控制字段
c = ctl.get();
}
// 当前线程池仍然处于运行状态 并且 将任务command添加到等待队列成功
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 如果当前状态时非运行状态:SHUTDOWN或者STOP等 并且 从队列中删除任务成功
if (! isRunning(recheck) && remove(command))
// 拒绝任务
reject(command);
// 进入到这里的条件是:
// 1)当前是运行状态
// 2)当前非运行状态,同时删除任务失败
// 当前已经没有worker了,创建新的worker,并且设置firstTask为null,因为这个任务已经添加到了队列中,然后调用getTask()获取这个任务。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 添加到队列中失败:可能的情况是采用有界队列或者直接提交队列。
else if (!addWorker(command, false))
reject(command);
}</span>
在接续往下讲之前来看看线程池是如何表示状态和工作线程数目的,
<span style="font-size:14px;"> private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
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; }
private static int ctlOf(int rs, int wc) { return rs | wc; }</span>
<span style="font-size:14px;"> private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 当某些异常情况,直接终止线程的生成
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
// 获取workerCount
int wc = workerCountOf(c);
// 当 1)workerCount >= CAPACITY
// 2)如果core为true,则workerCount >= corePoolSize
// 3)如果core为false,则workerCount >= corePoolSize
// 这三种情况下创建线程失败
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 利用cas原子操作使得c自增
if (compareAndIncrementWorkerCount(c))
// 如果自增成功,则直接中断内部循环和外部循环,回到标签retry处,然后不再执行retry,跳到
// Worker w = new Worker(firstTask);继续执行
break retry;
// 自增失败,从新获取c,因为其他线程可能修改了c
c = ctl.get(); // Re-read ctl
// 如果线程池状态改变了,那么直接中断内部和外部循环,回到标签retry处,然后继续从retry处往下执行。
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 修改ctl成功
// 创建新的Worker,worker其实就是工作线程。firstTask为其第一个需要执行的任务。
Worker w = new Worker(firstTask);
Thread t = w.thread;
// 获取锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
int rs = runStateOf(c);
if (t == null ||
(rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null))) {
// 异常情况下,workerCoutn--
decrementWorkerCount();
tryTerminate();
return false;
}
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
} finally {
mainLock.unlock();
}
// 启动线程
t.start();
// It is possible (but unlikely) for a thread to have been
// added to workers, but not yet started, during transition to
// STOP, which could result in a rare missed interrupt,
// because Thread.interrupt is not guaranteed to have any effect
// on a non-yet-started Thread (see Thread#interrupt).
if (runStateOf(ctl.get()) == STOP && ! t.isInterrupted())
t.interrupt();
return true;
}</span>
总的来说就是检查一个新的工作线程是否能添加到workers中,如果能,创建新的线程添加。如果能,则开始启动线程,运行
<span style="font-size:14px;"> final void runWorker(Worker w) {
Runnable task = w.firstTask; // 取得任务,如果是null,表示在生成worker的时候没有任务,则需要调用getTask()获取新的任务。
w.firstTask = null;
boolean completedAbruptly = true;
try {
// 工作线程主循环,对于在corePoolSize之内的工作线程,task永远不会是null,除非线程池终止。
// 对于corePoolSize之外的工作线程,如果允许timeout,则在keepAliveTime之后,还没有新的任务供该工作线程使用,则task为null。则该线程进入
// 终止模式
while (task != null || (task = getTask()) != null) {
w.lock();
clearInterruptsForTaskRun();
try {
beforeExecute(w.thread, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x)
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}</span>
那么线程池是如何检测超时的呢?
<span style="font-size:14px;"> private Runnable getTask() {
// 还未超时
boolean timedOut = false; // Did the last poll() time out?
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 异常情况
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
// 是否允许超时
boolean timed; // Are workers subject to culling?
for (;;) {
int wc = workerCountOf(c);
// 如果allowCoreThreadTimeOut 或者 wc > corePoolSize
// 前者表示所有的核心工作线程都允许timeout,这时不需要检查wc和corePoolSize的比较
// 如果核心工作线程不允许timeout,则需要比较wc和corePoolSize的大小,只要wc超过corePoolSize,也就是有多余的工作线程时候,都允许超时。
// 一旦wc降到corePoolSize的时候,超时机制被停止。
timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 还未超时,则终止循环
if (wc <= maximumPoolSize && ! (timedOut && timed))
break;
// 已经超时的时候,如果c--成功,则返回null
if (compareAndDecrementWorkerCount(c))
return null;
c = ctl.get(); // Re-read ctl
// c--失败,重新进行检查
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
try {
// 从workQueue取任务,如果timed也就是允许超时时,调用poll(keepAliveTime。。。),否则直接调用take。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
// 如果取任务成功
if (r != null)
return r;
// 否则,超时
timedOut = true;
} catch (InterruptedException retry) {
// 捕获InterruptedException,设置未超时
timedOut = false;
}
}
}</span>
5、ThreadPoolExecutor的终止
6、饱和策略
<span style="font-size:14px;">import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ExecutorTest {
public static void main(String[] args) {
ExecutorService esv = new ThreadPoolExecutor(2, 10, 1,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2), new ThreadFactory() {
private AtomicInteger count = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
// TODO Auto-generated method stub
return new Thread(r, "ArrayBlockingQueue " + count.incrementAndGet());
}
});
for (int i = 0; i < 25; i++) {
final int j = i;
esv.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.currentThread().sleep(1000);
System.out.println(j);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}
}</span>
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task ExecutorTest$2@1326484 rejected from java.util.concurrent.ThreadPoolExecutor@16546ef[Running, pool size = 10, active threads = 10, queued tasks = 2, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.reject(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.execute(Unknown Source)
at ExecutorTest.main(ExecutorTest.java:21)
0
1
5
4
6
8
10
7
9
11
2
3
可以看到抛出了异常。
0
8
6
4
10
7
5
9
1
11
12
3
2
13
15
14
16
21
17
18
20
19
23
22
24
1
11
9
7
5
0
8
6
4
10
23
24
修改策略为DiscardPolicy,
1
5
7
9
0
11
4
6
8
10
3
2
上面两个的结果很类似,不同的是后者放弃了新加入的任务,前者放弃了先加入的任务。