Java面试经典题:线程池专题

1、什么是线程池

线程池的基本思想是一种对象池,在程序启动时就开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

2、使用线程池的好处

减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

运用线程池能有效的控制线程最大并发数,可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

对线程进行一些简单的管理,比如:延时执行、定时循环执行的策略等,运用线程池都能进行很好的实现

3、线程池的主要组件

一个线程池包括以下四个基本组成部分:

线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;

工作线程(WorkThread):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;

任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;

任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

4、ThreadPoolExecutor类

讲到线程池,要重点介绍java.uitl.concurrent.ThreadPoolExecutor类,ThreadPoolExecutor线程池中最核心的一个类,ThreadPoolExecutor在JDK中线程池常用类UML类关系图如下:

我们可以通过ThreadPoolExecutor来创建一个线程池

new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,milliseconds,runnableTaskQueue,threadFactory,handler);

1. 创建一个线程池需要输入几个参数

corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。

maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。

runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。

ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字,Debug和定位问题时非常又帮助。

RejectedExecutionHandler(拒绝策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。n AbortPolicy:直接抛出异常。

keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。

TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS,千分之一毫秒)和毫微秒(NANOSECONDS,千分之一微秒)。

2. 向线程池提交任务

我们可以通过execute()或submit()两个方法向线程池提交任务,不过它们有所不同

execute()方法没有返回值,所以无法判断任务知否被线程池执行成功

threadsPool.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
});

submit()方法返回一个future,那么我们可以通过这个future来判断任务是否执行成功,通过future的get方法来获取返回值

try {
Object s = future.get();
} catch (InterruptedException e) {
   // 处理中断异常
   } catch (ExecutionException e) {
   // 处理无法执行任务异常
   } finally {
   // 关闭线程池
   executor.shutdown();
}

3. 线程池的关闭

我们可以通过shutdown()或shutdownNow()方法来关闭线程池,不过它们也有所不同

shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。

shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。

4. ThreadPoolExecutor执行的策略

线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务

线程数量达到了corePools,则将任务移入队列等待

队列已满,新建线程(非核心线程)执行任务

队列已满,总线程数又达到了maximumPoolSize,就会由(RejectedExecutionHandler)抛出异常

新建线程 -> 达到核心数 -> 加入队列 -> 新建线程(非核心) -> 达到最大数 -> 触发拒绝策略

5. 四种拒绝策略

AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满,线程池默认策略

DiscardPolicy:不执行新任务,也不抛出异常,基本上为静默模式。

DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行

CallerRunPolicy:拒绝新任务进入,如果该线程池还没有被关闭,那么这个新的任务在执行线程中被调用)

5、Java通过Executors提供四种线程池

CachedThreadPool():可缓存线程池。

线程数无限制

有空闲线程则复用空闲线程,若无空闲线程则新建线程 一定程序减少频繁创建/销毁线程,减少系统开销

FixedThreadPool():定长线程池。

可控制线程最大并发数(同时执行的线程数)

超出的线程会在队列中等待

ScheduledThreadPool():

定时线程池。

支持定时及周期性任务执行。

SingleThreadExecutor():单线程化的线程池。

有且仅有一个工作线程执行任务

所有任务按照指定顺序执行,即遵循队列的入队出队规则

1. newCachedThreadPool

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

public class ThreadPoolExecutorTest1 {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            final int index = i;
            try {
                Thread.sleep(index * 1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            cachedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName()+:+index);
                }
            });
        }
    }
}

2. newFixedThreadPool

newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待,指定线程池中的线程数量和最大线程数量一样,也就线程数量固定不变

示例代码如下

public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);// 每隔两秒打印3个数
        for (int i = 0; i < 10; i++) {
            final int index = i;
            fixedThreadPool.execute(new Runnable() {
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName()+:+index);
                        //三个线程并发
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

3. newscheduledThreadPool

newscheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下.表示延迟1秒后每3秒执行一次

public class ThreadPoolExecutorTest3 {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName() + : delay 1 seconds,and excute every 3 seconds);
            }
        },1,3,TimeUnit.SECONDS);// 表示延迟1秒后每3秒执行一次
    }
}

4. newSingleThreadExecutor

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

public class ThreadPoolExecutorTest4 {
    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(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

结果依次输出,相当于顺序执行各个任务。使用JDK自带的监控工具来监控我们创建的线程数量,运行一个不终止的线程,创建指定量的线程,来观察

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


在 Java 语言中,提高程序的执行效率有两种实现方法,一个是使用线程、另一个是使用线程池。而在生产环境下,我们通常会采用后者。为什么会这样呢?今天我们就来聊聊线程池的优点,以及池化技术及其应用。 1.池化技术 池化技术指的是提前准备一些资源,在需要时可以重复使用这些预先准备的资源。 池化技术的优点
在 Java 中停止线程的实现方法有以下 3 种: 自定义中断标识符,停止线程。 使用线程中断方法 interrupt 停止线程。 使用 stop 停止线程。 其中 stop 方法为 @Deprecated 修饰的过期方法,也就是不推荐使用的过期方法,因为 stop 方法会直接停止线程,这样就没有给
在多线程编程中,wait 方法是让当前线程进入休眠状态,直到另一个线程调用了 notify 或 notifyAll 方法之后,才能继续恢复执行。而在 Java 中,wait 和 notify/notifyAll 有着一套自己的使用格式要求,也就是在使用 wait 和 notify(notifyAll
在 Java 语言中,并发编程都是通过创建线程池来实现的,而线程池的创建方式也有很多种,每种线程池的创建方式都对应了不同的使用场景,总体来说线程池的创建可以分为以下两类: 通过 ThreadPoolExecutor 手动创建线程池。 通过 Executors 执行器自动创建线程池。 而以上两类创建线
sleep 方法和 wait 方法都是用来将线程进入休眠状态的,并且 sleep 和 wait 方法都可以响应 interrupt 中断,也就是线程在休眠的过程中,如果收到中断信号,都可以进行响应,并抛出 InterruptedException 异常。那 sleep 和 wait 的区别都有哪些呢
在 Java 中,线程的创建方法有 7 种,分为以下 3 大类: 继承 Thread 类的方式,它有 2 种实现方法。 实现 Runnable 接口的方式,它有 3 种实现方法。 实现 Callable 接口的方式,它有 2 种实现方法。 接下来我们一个一个来看。 1.继承Thread类 继承 Th
所谓的线程池的 7 大参数是指,在使用 ThreadPoolExecutor 创建线程池时所设置的 7 个参数,如以下源码所示: public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
在 Java 语言中,线程分为两类:用户线程和守护线程,默认情况下我们创建的线程或线程池都是用户线程,所以用户线程也被称之为普通线程。 想要查看线程到底是用户线程还是守护线程,可以通过 Thread.isDaemon() 方法来判断,如果返回的结果是 true 则为守护线程,反之则为用户线程。 我们
聊到线程池就一定会聊到线程池的执行流程,也就是当有一个任务进入线程池之后,线程池是如何执行的?我们今天就来聊聊这个话题。线程池是如何执行的?线程池的拒绝策略有哪些? 线程池执行流程 想要真正的了解线程池的执行流程,就得先从线程池的执行方法 execute() 说起,execute() 实现源码如下:
单例模式是面试中的常客了,它的常见写法有 4 种:饿汉模式、懒汉模式、静态内部类和枚举,接下来我们一一来看。 1.饿汉模式 饿汉模式也叫预加载模式,它是在类加载时直接创建并初始化单例对象,所以它并不存在线程安全的问题。它是依靠 ClassLoader 类机制,在程序启动时只加载一次,因此不存在线程安
线程安全是指某个方法或某段代码,在多线程中能够正确的执行,不会出现数据不一致或数据污染的情况,我们把这样的程序称之为线程安全的,反之则为非线程安全的。在 Java 中,解决线程安全问题有以下 3 种手段: 使用线程安全类,比如 AtomicInteger。 加锁排队执行 使用 synchronize
在 Java 语言中,保证线程安全性的主要手段是加锁,而 Java 中的锁主要有两种:synchronized 和 Lock,我们今天重点来看一下 synchronized 的几种用法。 用法简介 使用 synchronized 无需手动执行加锁和释放锁的操作,我们只需要声明 synchronize
在 Java 语言中,有两个线程池可以执行定时任务:ScheduledThreadPool 和 SingleThreadScheduledExecutor,其中 SingleThreadScheduledExecutor 可以看做是 ScheduledThreadPool 的单线程版本,它的用法和
从公平的角度来说,Java 中的锁总共可分为两类:公平锁和非公平锁。但公平锁和非公平锁有哪些区别?孰优孰劣呢?在 Java 中的应用场景又有哪些呢?接下来我们一起来看。 正文 公平锁:每个线程获取锁的顺序是按照线程访问锁的先后顺序获取的,最前面的线程总是最先获取到锁。 非公平锁:每个线程获取锁的顺序
单例模式的实现方法有很多种,如饿汉模式、懒汉模式、静态内部类和枚举等,当面试官问到“为什么单例模式一定要加 volatile?”时,那么他指的是为什么懒汉模式中的私有变量要加 volatile? 懒汉模式指的是对象的创建是懒加载的方式,并不是在程序启动时就创建对象,而是第一次被真正使用时才创建对象。
读写锁(Readers-Writer Lock)顾名思义是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,因为读操作本身是线程安全的,而写锁则是互斥锁,不允许多个线程同时获得写锁,并且写操作和读操作也是互斥的。总结来说,读写锁的特点是:读读不互斥、读写互斥、写写互斥。 1.读写锁使用 在
很多场景下,我们需要等待线程池的所有任务都执行完,然后再进行下一步操作。对于线程 Thread 来说,很好实现,加一个 join 方法就解决了,然而对于线程池的判断就比较麻烦了。 我们本文提供 4 种判断线程池任务是否执行完的方法: 使用 isTerminated 方法判断。 使用 getCompl
在 Java 中,线程池的状态和线程的状态是完全不同的,线程有 6 种状态:NEW:初始化状态、RUNNABLE:可运行/运行状态、BLOCKED:阻塞状态、WAITING:无时限等待状态、TIMED_WAITING:有时限等待状态和 TERMINATED:终止状态。而线程池的状态有以下 5 种:
volatile 是 Java 并发编程的重要组成部分,也是常见的面试题之一,它的主要作用有两个:保证内存的可见性和禁止指令重排序。下面我们具体来看这两个功能。 内存可见性 说到内存可见性问题就不得不提 Java 内存模型,Java 内存模型(Java Memory Model)简称为 JMM,主要
1.第一范式 第一范式规定表中的每个列都应该是不可分割的最小单元。比如以下表中的 address 字段就不是不可分割的最小单元,如下图所示: 其中 address 还可以拆分为国家和城市,如下图所示: 这样改造之后,上面的表就满足第一范式了。 2.第二范式 第二范式是在满足第一范式的基础上,规定表中