java多线程面试(三)

发布时间:2024年01月17日

1.Exchanger 了解吗

Exchanger(交换者)是用于一个线程之间的协调工具类,Exchannge用于进行线程之间的数据交换,它提供一个同步点,两个线程可以交换彼此的数据。
假如两个线程有一个没有执行exchange()方法,则会一直等待,如果担心有特殊情况发生,避免一直等待,可以使用
exchange(Vx, longtimeOut,TimeUnitunit)设置最大等待时长。

2.什么是线程池

  1. 帮助管理线程池,避免增加创建线程和销毁线程的开销
  2. 提高响应速度
  3. 重复利用

3.线程池的主要参数

  1. corePoolSize核心线程数
  2. maximumPoolSize最大线程数
  3. keepAliveTime 线程存活时间
  4. unit 时间单位
  5. workQueud 等待队列
  6. threadFactory 线程工厂
  7. handlr 拒绝策略

4.有哪几种常用线程池线程池

  1. newFixThreadPool(固定数目线程池)FixedThreadPool 适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,
    即适用执行长期的任务。
  2. newCacheTheadPool可缓存线程池 ,用于并发执行大量短期的小任务
  3. newScheduledThreadPool定时及周期线程池,周期性执行任务的场景,需要限制线程数量的场景
  4. newSingleThreadExecutor 单线程池 适用于串行执行任务的场景,一个任务一个任务地执行

5.线程池提交execute和submit的区别

  1. execute用于提交不需要返回值的。
threadsPool.execute(new Runnable()
 { @Override public void run() {
 // TODO Auto-generated method stub }
 });
  1. submit()用于需要返回值的任务,线程池会返回一个Future类型的对象,通过这个对象future对象可以判断任务是否执行成功,并且可以通过future的get方法来获取返回值。
Future<Object> future = executor.submit(harReturnValuetask);
 try { Object s = future.get(); } catch (InterruptedException e) {
 // 处理中断异常
} catch (ExecutionException e) {
 // 处理无法执行任务异常
} finally {
 // 关闭线程池 executor.shutdown();
 }

6.线程池如何关闭

可以通过调用线程池的shutdown和shutdownNew方法来关闭线程池,它们的原理都是遍历线程池中的工作线程,然后逐步调用线程的interrupt方法来中断线程,所以无法响应中断的任务永远无法中止。
shutdown 和shutdownnow简单来说区别如下:

  • shutdownNow()能立即停止线程池,正在跑的和正在等待的任务都停下了。这样做立即生效,但是风险也比较大。
  • shutdown()只是关闭了提交通道,用submit()是无效的;而内部的任务该怎么跑还是怎么跑,跑完再彻底停止线程池

7.线程池的异常处理方式

在这里插入图片描述

8.线程池的状态

线程池有RUNNING.SHUTDOWN,STOP,TIDYING,TERMNATED
在这里插入图片描述

9.Fork/Join框架了解吗

Fork/Join框架是java7之后提供的一个用于并行的框架,是一个把大任务分解为若干小任务,最终汇总每个小任务结果后得到大任务的框架。
掌握Fork/Join需要理解两个点 分而治之和工作窃取算法
分而治之::将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相
互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解
分而治之

工作窃取算法:大任务拆成了若干个小任务,把这些小任务放到不同的队列里,各自创建单独线程来执行队列里的任务。
那么问题来了,有的线程干活块,有的线程干活慢。干完活的线程不能让它空下来,得让它去帮没干完活的线程干活。
它去其它线程的队列里窃取一个任务来执行,这就是所谓的工作窃取。
工作窃取发生的时候,它们会访问同一个队列,为了减少窃取任务线程和被窃取任务线程之间的竞争,通常任务会使
用双端队列,被窃取任务线程永远从双端队列的头部拿,而窃取任务的线程永远从双端队列的尾部拿任务执行。
在这里插入图片描述

看一个Fork/Join框架应用的例子,计算1~n之间的和:1+2+3+…+n

  • 设置一个分割阈值,任务大于阈值就拆分任务
  • 任务有结果,所以需要继承RecursiveTask
package com.liu.ThreadPool;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

/**
 * @description:
 * @author: LCY
 * @time: 2024-01-15 14:31
 **/
public class ForkJoin extends RecursiveTask {
    /**
     * The main computation performed by this task.
     *
     * @return the result of the computation
     */
    private static final int THRESHOLD = 16;//阈值
    private int start;
    private int end;

    public ForkJoin(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Object compute() {

        int sum = 0;
        //如果任务足够小就计算任务
        boolean canCompute = (end - start) <= THRESHOLD;
        if (canCompute) {
            for (int i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            //如果任务大于阈值,就分裂成两个子任务计算
            int middle = (start + end) / 2;
            ForkJoin leftTask = new ForkJoin(start, middle);
            ForkJoin rightTask = new ForkJoin(middle + 1, end);
            //执行子任务
            leftTask.fork();
            // 看一个Fork/Join框架应用的例子,计算1~n之间的和:1+2+3+…+n
            //设置一个分割阈值,任务大于阈值就拆分任务
            // 任务有结果,所以需要继承RecursiveTask
            rightTask.fork();//等待子任务执行完,并得到其结果
            int leftResult = (int) leftTask.join();
            int rightResult = (int) rightTask.join();//合并子任务
            sum = leftResult + rightResult;
        }
        return sum;
    }


    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = new ForkJoinPool();//生成一个计算任务,负责计算

        ForkJoin task = new ForkJoin(1, 100); //执行一个任务
        Future<Integer> result = forkJoinPool.submit(task);
        try {
            System.out.println(result.get());
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
        }
    }
}

ForkJoinTask与一般Task的主要区别在于它需要实现compute方法,在这个方法里,首先需要判断任务是否足够小,如
果足够小就直接执行任务。如果比较大,就必须分割成两个子任务,每个子任务在调用fork方法时,又会进compute
方法,看看当前子任务是否需要继续分割成子任务,如果不需要继续分割,则执行当前子任务并返回结果。使用
join方法会等待子任务执行完并得到其结果。

ps:喜欢请点点关注,你的赞是我前进的动力。

文章来源:https://blog.csdn.net/weixin_45003206/article/details/135594202
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。