问题
今天碰到一个有意思的问题,我把它抽取出来:
package thread_pool;
import java.util.concurrent.*;
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService threadPoolExecutor = new ThreadPoolExecutor(2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
threadPoolExecutor.execute(()->{
System.out.println("the thread is running...");
});
}
}
搞了个线程池,分配给它一个任务,就是打印一句话,控制台“ the thread is running”当然打印了,但是,程序没有结束:
问题是:
- 为什么阻塞了?
- 在哪里阻塞了?
线程池的知识
我们可以稍稍聊聊线程池。
池化技术,像连接池、线程池,都是为了管理资源,就像巴士公司对于巴士的管理。当乘客人满了,巴士才会发车开往机场。这里的一辆辆巴士,其实就是一条条线程。当巴士完成工作了,它又停回原地了。
像Arrays、Collections这些工具类,线程也有自己的工具类,那就是Executors。
线程池可以直接通过工具类来创建:
newFixedThreadPool
定长的线程池。
package thread_pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestExecutors {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName() + " is running...");
};
for (int i = 1; i <= 6; i++) {
executor.execute(runnable);
}
}
}
我们搞5个线程,然后丢6个任务进去,最后工作的肯定就只有5个线程:
pool-1-thread-1 is running...
pool-1-thread-5 is running...
pool-1-thread-1 is running...
pool-1-thread-2 is running...
pool-1-thread-4 is running...
pool-1-thread-3 is running...
newCachedThreadPool
长度可伸缩的线程池。
package thread_pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestExecutors {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName() + " is running...");
};
for (int i = 1; i <= 20; i++) {
executor.execute(runnable);
}
}
}
丢20个任务,我们看看结果:
pool-1-thread-1 is running...
pool-1-thread-3 is running...
pool-1-thread-2 is running...
pool-1-thread-4 is running...
pool-1-thread-6 is running...
pool-1-thread-7 is running...
pool-1-thread-8 is running...
pool-1-thread-5 is running...
pool-1-thread-6 is running...
pool-1-thread-1 is running...
pool-1-thread-5 is running...
pool-1-thread-1 is running...
pool-1-thread-4 is running...
pool-1-thread-8 is running...
pool-1-thread-2 is running...
pool-1-thread-6 is running...
pool-1-thread-9 is running...
pool-1-thread-10 is running...
pool-1-thread-7 is running...
pool-1-thread-3 is running...
有10条线程在工作。
newSingleThreadExecutor
只有一条线程。
package thread_pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestExecutors {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName() + " is running...");
};
for (int i = 1; i <= 10; i++) {
executor.execute(runnable);
}
}
}
10个任务只有一条线程在工作:
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
newScheduledThreadPool
有延迟地执行任务或有周期地执行任务。
延迟执行任务:
package thread_pool;
import java.util.Date;
import java.util.concurrent.*;
public class TestExecutors {
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName() + " is running..." + " time : " + new Date());
};
System.out.println(Thread.currentThread().getName() + " is running..." + " time : " + new Date());
executor.schedule(runnable, 5, TimeUnit.SECONDS);
executor.schedule(runnable, 10, TimeUnit.SECONDS);
}
}
结果:
main is running... time : Mon Mar 02 21:38:10 GMT+08:00 2020
pool-1-thread-1 is running... time : Mon Mar 02 21:38:15 GMT+08:00 2020
pool-1-thread-2 is running... time : Mon Mar 02 21:38:20 GMT+08:00 2020
显然是每隔5秒执行一次任务。
或者可以这么写:
package thread_pool;
import java.util.Date;
import java.util.concurrent.*;
public class TestExecutors {
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName() + " is running..." + " time : " + new Date());
};
System.out.println(Thread.currentThread().getName() + " is running..." + " time : " + new Date());
executor.scheduleAtFixedRate(runnable, 2, 5, TimeUnit.SECONDS);
TimeUnit.SECONDS.sleep(20);
executor.shutdown();
}
}
初始延迟两秒,每个任务之间相隔五秒,20秒之后关闭线程池。
main is running... time : Mon Mar 02 21:44:45 GMT+08:00 2020
pool-1-thread-1 is running... time : Mon Mar 02 21:44:47 GMT+08:00 2020
pool-1-thread-1 is running... time : Mon Mar 02 21:44:52 GMT+08:00 2020
pool-1-thread-1 is running... time : Mon Mar 02 21:44:57 GMT+08:00 2020
pool-1-thread-2 is running... time : Mon Mar 02 21:45:02 GMT+08:00 2020
但是,我们不能这么建线程池,如阿里规范所说:
默认的方式队列长度是整型的最大值:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
所以我们要用ThreadPoolExecutor
。
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
我们要了解其中的参数含义。
举个银行办理业务的例子:
银行一共有5个柜台处理业务,这就是maximumPoolSize
。
现在开放2个,也就是一号柜台和二号柜台,其余三个都是闲置状态(工作人员可能去吃午饭了)。这正在工作的2个柜台叫做corePoolSize
。
然后还有侯客区,侯客区是等待办理业务的人坐的地方,这里侯客区的大小是3。侯客区对应线程池里的workQueue
。
银行就相当于threadFactory
。线程工厂是用来创建线程的,就像银行是用来组织工作人员的。以前你跑一个线程需要new Thread(Runnable r)
,现在传一个线程工厂就行了。ThreadFactory
是一个接口,你可以这么去实现:
class SimpleThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
return new Thread(r);
}
当然,我们有默认的工厂。对于这个参数,传一个默认的就行了。
如果5个柜台同时办公,并且侯客区坐满了人,那么如果此时新来一个人要办业务,就必须要拒绝他,如何拒绝他,就是这里的拒绝策略RejectedExecutionHandler handler
。你可以让他等,让他走,等等的,我们下面讲。
如果2个工作的柜台都在工作,并且侯客区坐满了人,此时新来一个人,那么银行就会新开一个柜台受理业务(比如三号柜台)。等那个人走了,三号柜台不可能一直开着,它要等一会关闭的,要等多久呢,就是keepAliveTime
,时间单位由unit
给定。
我们一个一个场景看:
有两个人来办理业务
package thread_pool;
import java.util.concurrent.*;
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService threadPoolExecutor = new ThreadPoolExecutor(2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Runnable runnable = ()->{
System.out.println(Thread.currentThread().getName()+ "正在受理业务...");
};
for (int i = 1; i <= 2; i++) {
threadPoolExecutor.execute(runnable);
}
}
}
有两个人来办理业务,因为开放的柜台就两个,所以就由这两个柜台来受理业务:
pool-1-thread-1正在受理业务...
pool-1-thread-2正在受理业务...
如果有第三个人来,那么他会先坐在侯客室(阻塞队列),等到1号或者2号柜台办理完成后,他就可以去办理了:
pool-1-thread-2正在受理业务...
pool-1-thread-2正在受理业务...
pool-1-thread-1正在受理业务...
现在有六个人来办理(for循环中i<=6
),侯客室坐不下了,银行不得不再开一个窗口来受理,因此会有三条线程在工作。
pool-1-thread-2正在受理业务...
pool-1-thread-2正在受理业务...
pool-1-thread-2正在受理业务...
pool-1-thread-2正在受理业务...
pool-1-thread-1正在受理业务...
pool-1-thread-3正在受理业务...
但是这个也不是绝对的,你可以想一下,如果1号窗口受理的快,它照样可以办理第三个人的业务,最后可能还是两个窗口在受理。
如果开了第三个窗口(就像我打印出来的),因为我们设置了keepAliveTime
为两秒,所以两秒后,第三个窗口会关掉,活跃线程数会减一。
for (int i = 1; i <= 6; i++) {
threadPoolExecutor.execute(runnable);
System.out.println("活跃线程数(执行中): " + Thread.activeCount());
}
TimeUnit.SECONDS.sleep(3);
System.out.println("活跃线程数(执行后): " + Thread.activeCount());
为了让效果明显,主线程我睡了三秒。
结果:
活跃线程数(执行中): 3
活跃线程数(执行中): 4
活跃线程数(执行中): 4
活跃线程数(执行中): 4
活跃线程数(执行中): 4
活跃线程数(执行中): 5
pool-1-thread-1正在受理业务...
pool-1-thread-1正在受理业务...
pool-1-thread-1正在受理业务...
pool-1-thread-1正在受理业务...
pool-1-thread-2正在受理业务...
pool-1-thread-3正在受理业务...
活跃线程数(执行后): 4
因为jvm中一直有main和gc两条线程,所以我们减二来看就对了。
执行时最高开到了五条线程,执行后线程数少一,因为多开的窗口关掉了。
现在我要演示拒绝策略,理论上说,8个人是受理的极限了,因为最大线程数+阻塞队列大小为8。但是也可能承受住9或者10。
所以我们直接来15吧。
Exception in thread "main"
java.util.concurrent.RejectedExecutionException:
直接报错了。
拒绝策略有四种:
抛出异常:
/**
* A handler for rejected tasks that throws a
* {@code RejectedExecutionException}.
*/
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
相当于银行无法受理更多的人,于是发出警报。
哪来的回哪儿去
/**
* A handler for rejected tasks that runs the rejected task
* directly in the calling thread of the {@code execute} method,
* unless the executor has been shut down, in which case the task
* is discarded.
*/
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
让9个人过来:
for (int i = 1; i <= 9; i++) {
threadPoolExecutor.execute(runnable);
}
ExecutorService threadPoolExecutor = new ThreadPoolExecutor(2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
这时候的结果为:
main正在受理业务...
pool-1-thread-1正在受理业务...
pool-1-thread-1正在受理业务...
pool-1-thread-1正在受理业务...
pool-1-thread-1正在受理业务...
pool-1-thread-2正在受理业务...
pool-1-thread-3正在受理业务...
pool-1-thread-5正在受理业务...
pool-1-thread-4正在受理业务...
任务是main线程派来的,就回到main线程去处理。
不理你
/**
* A handler for rejected tasks that silently discards the
* rejected task.
*/
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
银行对你不理不睬,啥也不做,能处理多少就处理多少。
这种策略不会有异常。
尝试退掉第一个人让你进来
/**
* A handler for rejected tasks that discards the oldest unhandled
* request and then retries {@code execute}, unless the executor
* is shut down, in which case the task is discarded.
*/
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardOldestPolicy} for the given executor.
*/
public DiscardOldestPolicy() { }
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
这种情况是,线程池让第一个人走开,然后让你进来,它会尝试,如果尝试成功,就这么做,否则就不管你。
举个例子:
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,1,0,TimeUnit.MILLISECONDS
,new ArrayBlockingQueue<>(2),new ThreadPoolExecutor.DiscardOldestPolicy());
executor.execute(()->{
System.out.println("a simple task...");
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
executor.execute(()->{queue.offer("First");});
executor.execute(()->{queue.offer("Second");});
executor.execute(()->{queue.offer("Third");});
TimeUnit.SECONDS.sleep(2);
List<String> results = new ArrayList<>();
queue.drainTo(results);
System.out.println(results);
第一个任务占了100毫秒,第二个任务往queue中加一个“First”,第三个任务加入了“Second”,当第四个任务进来,由于DiscardOldestPolicy
,尝试移除“First”,腾出空间给第四个任务。
结果:
a simple task...
[Second, Third]
回归问题
我们讲了线程池的知识,但是最后还要回到最初的阻塞问题。
其实,上面的例子我都没有写threadPoolExecutor.shutdown();
,
正确的语法是:
ExecutorService threadPoolExecutor = new ThreadPoolExecutor(2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
try {
threadPoolExecutor.execute(() -> {
System.out.println("the thread is running...");
});
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPoolExecutor.shutdown();
}
虽然我知道语法,但是我还是对这个阻塞很感兴趣。
打开java VisualVM,我们找到该线程池的thread dump:
"pool-1-thread-1" #11 prio=5 os_prio=0 tid=0x0000000057fdc000 nid=0x1198 waiting on condition [0x000000005895e000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000ec408fd8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.ArrayBlockingQueue.take(ArrayBlockingQueue.java:403)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
估计是停在java.util.concurrent.ArrayBlockingQueue.take
这里了。
debug走一波:
终于知道了,是BlockingQueue干的事。
来源:CSDN
作者:weixin_43810802
链接:https://blog.csdn.net/weixin_43810802/article/details/104618731