Java多线程面试题
1. 并行和并发有什么区别?
- 并行:多个处理器或多核处理器同时处理多个任务。
- 并发:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行。
2. 线程的6种状态?
-
NEW 初始状态,线程被构建,但是没有调用 start() 方法。
-
RUNNABLE 运行状态,就绪和运行两种状态统称作“运行中”。
-
BLOCKED 阻塞状态(被同步锁或者 IO 锁阻塞)
-
WAITING 等待状态,需要被唤醒。
-
TIMED_WAITING 超时等待状态。时间到自动唤醒。
-
TERMINATED 终止状态,表示当前线程已经执行完毕。
3. stop() 和 resume(),suspend() 方法为何不推荐使用?
stop()
停止,resume()
恢复,suspend()
暂停,这些方法在调用后不会释放资源(比如锁)。
4. sleep() 和 wait() 有什么区别?
-
sleep()
Thread.sleep()
来自Thread 类的方法,不会释放锁,时间到会自动恢复。 -
wait()
Object.wait()
来自 Object类的方法,会主动释放锁。使用notify()
或notifyAll()
直接唤醒。
5. run() 和 start() 区别?
-
start() 方法用于启动线程,只能调用一次。
HelloThread thread = new HelloThread(); thread.start();
-
run() 方法用于执行线程的运行时代码。可以重复调用。
HelloThread thread = new HelloThread(); thread.run(); thread.run();
6. 线程池具有哪些状态?
-
RUNNING 这是最正常的状态,接受新的任务,处理等待队列中的任务。
-
SHUTDOWN 不接受新的任务提交,但是会继续处理等待队列中的任务。
-
STOP 不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
-
TIDYING 所有的任务都销毁了,
workCount
为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法terminated()
。 -
TERMINATED
terminated()
方法结束后,线程池的状态就会变成这个。
7. shutdown()
和 shutdownNow()
区别?
-
shutdown()
会将线程池的状态设置为SHUTDOWN ,不接受新的任务提交,但是会继续处理等待队列中的任务。
-
shutdownNow()
会将线程池的状态设置为STOP ,不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
8. 创建线程池的几个核心构造参数?
//corePoolSize 核心线程池大小
//maximumPoolSize 最大线程池大小
//keepAliveTime 最大存活时间
//unit keepAliveTime时间单位
//workQueue 阻塞队列
//ThreadFactory 线程工厂
//RejectedExecutionHandler 拒绝策略
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(20, 50, 30,
TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(10),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
9. ArrayBlockingQueue 和 LinkedBlockingQueue区别,各有什么好处?各在什么情况下使用?
-
队列大小初始化方式不同
ArrayBlockingQueue
是有界的,必须指定队列的大小;
LinkedBlockingQueue
是无界的,可以不指定队列的大小,但是默认是Integer.MAX_VALUE
。 -
实现不同
ArrayBlockingQueue
基于数组结构,LinkedBlockingQueue
基于链表结构。
10. 线程池的四种拒绝策略?
AbortPolicy
默认策略,丢弃任务并抛出RejectedExecutionException
异常 。DiscardPolicy
丢弃任务,但是不抛出异常。DiscardOldestPolicy
喜新厌旧,丢弃队列最前面的任务,然后重新提交被拒绝的任务。CallerRunsPolicy
由调用者的线程处理该任务。
11. 线程池中的线程是怎么创建的?是一开始就随着线程池的启动创建好的吗?
不是。线程池默认初始化后不启动Worker,等待有请求时才启动。
每当我们调用execute()方法添加一个任务时,线程池会做如下判断:
- 如果正在运行的线程数量小于
corePoolSize
,那么马上创建线程运行这 个任务; - 如果正在运行的线程数量大于或等于
corePoolSize
,那么将这个任务放 入队列; - 如果这时候队列满了,而且正在运行的线程数量小于
maximumPoolSize
,那么还是要创建非核心线程立刻运行这个任务; - 如果队列满了,而且正在运行的线程数量大于或等于
maximumPoolSize
,那么线程池默认拒绝策略是会抛出异常RejectExecutionException
。 - 当一个线程完成任务时,它会从队列中取下一个任务来执行。当一个线程无事可做,超过一定的时间
keepAliveTime
,线程池会判断如果当前运行的线程数大于corePoolSize
,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize
的大小。
12. Java 中默认实现好的线程池又有哪些呢?请比较它们的异同。
-
newCachedThreadPool()
创建一个可缓存的线程池ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
-
newFixedThreadPool(10)
创建一个定长线程池ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
-
newScheduledThreadPool(10)
创建一个定长线程池,可以定时或周期性的执行任务ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
-
newSingleThreadExecutor()
创建一个使用单个 worker 线程的 ExecutorExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
阿里巴巴开发手册中指明了建议使用 ThreadPoolExecutor
方式创建线程池。而不建议使用Executors。
13. 如何在 Java 线程池中提交线程?
线程池最常用的提交任务的方法有两种:
-
execute 无返回值
cachedThreadPool.execute(new Runnable() { @Override public void run() { } });
-
submit 可以有返回值
Future<Object> submit = cachedThreadPool.submit(new Callable<Object>() { @Override public Object call() throws Exception { return new Object(); } });
14. 线程池中 execute() 和 submit()方法有什么区别?
- execute():只能执行 Runnable 类型的任务。
- submit():可以执行 Runnable 和 Callable 类型的任务。 Callable 类型的任务可以获取执行的返回值,而 Runnable 执行无返回值。