java线程池
四种常用的线程池
-
Excutors.newSingleThreadExecutor 单个线程的线程池。
- Excutors.newFixedThreadPool 固定数量的线程池,处理的任务超过线程数量会进行等待。
- Excutors.newCacheThreadExecutor 可缓存线程池。
- Excutors.newScheduleThreadExecutor 主要完成定时或者周期的执行线程任务。
无论以上哪一种线程池,内部都是使用的ThreadPoolExecutor这个类,我们来看一下他的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:核心线程数量,即使空闲也不会回收
maximumPoolSize:最大线程数量,当workQueue队列满时并且当前线程数量小于maximumPoolSize,创建新的线程
keepAliveTime:当空闲时间达到keepAliveTime时,会销毁线程,直到等于corePoolSize 默认是60s
unit:时间类型,分为天,时,分,秒,毫秒,微秒,纳秒。配合keepAliveTime使用
workQueue:线程等待队列 前面介绍了四种线程池使用的Queue
threadFactory: 创建线程的工厂 ,默认使用new DefaultThreadFactory()
handler:拒绝策略。当队列满且不能创建线程时使用的策略。默认使用new AbortPolicy(); 抛出异常
阻塞队列BlockQueue
LinkedBlockingQueue
以单链表实现的阻塞队列,它是线程安全的,借助分段的思想把入队出队分裂成两个锁。
通常使用的构造函数有无参,和传入一个int类型。如果是无参,队列的长度默认是Integer.MAX_VALUE;
put方法,当队列满了之后,会调用notFull.await()进行阻塞,唤醒之后会入队。
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
take方法,当队列是空时,会阻塞。当前元素>1时,会唤醒其他线程去工作。
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
SynchronousQueue
无缓冲队列。并不是真正的队列,不维护存储空间,维护的是一组线程。当线程A调用put方法,此时线程会阻塞,直到另一个线程调用了take方法,A线程才会被唤醒。 同样的道理,当线程B执行take方法时(),此时队列里面是没有数据的,线程B也会阻塞,直到另外一个线程调用了put方法往队列里面添加元素才会被唤醒。
DelayedWorkQueue
优先级队列DelayedWorkQueue,保证添加到队列中的任务,会按照任务的延时时间进行排序,延时时间少的任务首先被获取。
四种线程池的参数
-
Excutors.newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
核心线程和最大线程数都是1,超时时间是0s。内部使用的LinkedBlockingQueue,队列大小是Integer.MAX_VALUE;
-
Excutors.newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
核心线程和最大线程数都是用户传来的数量,超时时间是0s。内部使用的LinkedBlockingQueue,队列大小是Integer.MAX_VALUE;
-
Excutors.newCacheThreadExecutor
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
核心线程数是0,最大线程数是Integer.MAX_VALUE,超时时间是60s,内部使用的SynchronousQueue。表示只要有任务就会立刻执行,当没有线程空闲时就会创建线程,并且会立刻调用take方法,保证队列里的消息立马被消费,生产者才不会阻塞。超时不用的线程会被会回收
-
Excutors.newScheduleThreadExecutor
public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
核心线程数是用户传来的,最大线程数是Integer.MAX_VALUE,超时时间是0ns,内部使用的是DelayedWorkQueue
阿里巴巴规范不推荐使用这四种线程池的原因
以上四种线程池都有一个特点,就是生产者可以无限的往线程池中放置任务,要么队列是无限,要么是maximumPoolSize是无限。这样就可能导致我们的业务场景下会无限的创建线程或者在队列里面存放消息,导致OOM。
正确的做法应该是自己创建线程池ThreadPoolExecutor,根据业务场景自定义参数 。
线程池的工作模式
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
for (int i = 0; i < 100; i++) {
final int j =i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(j);
}
});
}
}
以此程序为例,介绍线程池的工作流程。(这里所说的创建线程指的是操作系统内核级的线程,并不是new的java对象。只有调用start()方法,才会创建内核线程)
-
Executors.newFixedThreadPool(2);这一步不会创建线程,只是定义一些线程的参数和初始化队列。核心线程数是2
-
进入第一次循环,执行execute方法,传入一个java线程对象。会先判断创建的内核线程是否小于核心线程,如果小于核心线程,则调用addWorker方法创建线程。
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { //如果小于核心线程,则调用addWorker方法创建核心线程 command是传入的线程 if (addWorker(command, true)) return; c = ctl.get(); } 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); } else if (!addWorker(command, false)) reject(command); }
-
addWorker方法中,首先会new Worker(), Worker类实现了Runnable接口,表示也是一个线程,它里面维护了两个很重要的参数thread和firstTask,其中thread就是Work线程,firstTask是我们传入的线程。
final Thread thread; Runnable firstTask; Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); }
-
下面是addWorker方法的核心代码。创建完Worker线程之后,下面调用了workers.add(w); workers是当前正在工作的线程。再往下面就调用了t.start(),调用了start()方法相当于创建一个核心线程;
boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { //firstTask是我们传入的线程 w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { 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 rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); }
-
接下来我们来看Worker类中的run方法,调用了runWorker,里面有个while循环,判断条件是task != null || (task = getTask()) != null,此时task是我们传入的线程,肯定不是空,所以会执行下面的 方法,调用的是task.run()。调用run方法是不会创建核心线程的,但是也会执行run方法中的内容,表示我们传入的线程代码正在执行。
public void run() { runWorker(this); }
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, 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); } }
-
我们再来看一下getTask()方法。核心代码 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
如果超时了并且在队列中获取不到线程,会返回空。否则会调用workQueue.take(),如果队列中是空,则会阻塞
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? 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; } int wc = workerCountOf(c); // Are workers subject to culling? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
-
现在第一次循环结束了,由于核心线程设置了是2,所以第二次循环依旧会创建内核线程。当进入第三次循环时,再来看excute执行的代码。第一次判断不通过,然后往队列里面添加元素workQueue.offer(command)。此时队列里面有了元素,第六步骤中的阻塞获取线程元素就能获取到,然后再调用队列中线程的run方法。
//判断当前工作的线程是否小于核心线程 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } //将线程放入队列 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); }
总结一句话:** 线程池中运行的线程并不是用户创建的线程,而是创建线程池中内置的Worker线程对象,调用worker.start()创建内核线程,在worker.run()方法中会获取队列中的线程,执行队列中线程的run方法 **