面试官最喜欢问的并发编程专题

什么是线程池

操作系统创建线程、切换线程状态、结束线程是需要时间,且消耗CPU和内存资源的。在高并发环境中,线程不断地创建和销毁,造成了系统资源的很大的浪费。为了解决这个问题,Java提出了一种线程池的概念。

线程池可以允许线程运行完后不立即销毁,让线程重复使用,继续执行其他的任务,提高效率且节约内存。类似的做法如数据库的连接池。


聊聊线程池 - 面试官最喜欢问的并发编程专题

线程池的作用:

  • 降低资源消耗 通过重复利用已创建的线程,降低创建和销毁线程造成的系统资源消耗
  • 提高响应速度 当任务到达时,任务可以不需要等到线程创建就能立即执行
  • 提高线程的可管理性 线程是稀缺资源,如果过多地创建,不仅会消耗系统资源,还会降低系统的稳定性,导致使用线程池可以进行统一分配、调优和监控。

线程池的实现原理

当向线程池提交一个任务之后,线程池是如何处理这个任务的呢?本节来看一下线程池 的主要处理流程,处理流程图所示。

从图中可以看出,当提交一个新任务到线程池时,线程池的处理流程如下。

1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。

2)线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

3)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程 来执行任务。如果已经满了,则交给饱和策略来处理这个任务。


聊聊线程池 - 面试官最喜欢问的并发编程专题


ThreadPoolExecutor执行execute()方法的代码:

<code>int c = ctl.get();//当前线程数
//当前线程数小于基本线程数,则创建线程并执行当前任务
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);   //检查当前状态是否可以增加任务到池中
}
else if (!addWorker(command, false)) //检查当前状态是否可以增加任务到池中
    reject(command);//如果增加任务失败,抛出异常/<code>

执行过程中,要维护一个任务队列,当线程处理完一个任务后,会从队列中获取下一个要处理的任务。

ThreadPoolExecutor执行任务示意图

聊聊线程池 - 面试官最喜欢问的并发编程专题


创建线程池

最简单的方式通过工厂类Executors来创建线程池

1、可缓存线程池

Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务

<code>public class NewCachedThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cachedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName()
                            + "正在被执行");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}/<code>

2、固定个数的线程池

Executors.newFixedThreadPool(int n):创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。

<code>public class NewFixedThreadPoolTest {
static AtomicInteger count=new AtomicInteger(0);
public static void main(String[] args) {
// 创建一个可重用固定个数的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
for (int i = 0; i < 10; i++) {
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
count.getAndIncrement();
// 打印正在执行的缓存线程信息
System.out.println(Thread.currentThread().getName()
+ "正在被执行:"+count.get());
Thread.sleep(500);
//count=count+1;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
fixedThreadPool.shutdown();//关闭线程池
}
}/<code>

3、单线程化的线程池

它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

<code>public class NewSingleThreadExecutorTest {
public static void main(String[] args) {
//创建一个单线程化的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
//结果依次输出,相当于顺序执行各个任务
System.out.println(Thread.currentThread().getName() + "正在被执行,打印的值是:" + index);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
singleThreadExecutor.shutdown();
}
}/<code>

上面的例子使用的是execute()方法提交任务。

execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。 通过以下代码可知execute()方法输入的任务是一个Runnable类的实例。

还可以使用submit()方法提交任务,submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

关于线程池,你还有其他问题吗?欢迎提问交流。


分享到:


相關文章: