Java多线程②——多线程知识梳理

多线程的常用实现/常用的线程池

根据阿里对使用线程池的规范:可以参考Executors的实现,按照业务实现自己的线程池。

注意线程池等资源还是要想着关闭。

初始化时,来一个任务新建一个一个线程;直到核心线程数满,再往队列里面放任务;如果队列也满了就继续新建线程到最大线程数量;如果最大线程数满就使用拒绝策略;Executor的默认拒绝策略是AbortPolicy;Spring线程池默认是同步执行。

public static void main(String[] args) {
    int threadCount = 10;
    // 依次设置核心线程数、最大线程数、(大于核心数部分)空闲存活时间、存活时间单位、队列
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(threadCount, threadCount, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
    //固定大小线程池、
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    //无界线程池
    Executors.newCachedThreadPool();
    //单个线程池  只有一个线程池的固定大小线程池FIFO
    Executors.newSingleThreadExecutor();

    /** ---------------- 其他线程池--------------**/
    //用于任务调度的线程池    
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
    //工作窃取线程池
    ExecutorService executorServiceFk = Executors.newWorkStealingPool();
}

拒绝策略

  • CallerRunsPolicy 同步执行(调用当前线程池的所在的线程去执行被拒绝的任务)
  • AbortPolicy 抛出个RejectedExecutionException异常,不执行此任务
  • DiscardPolicy 直接抛弃任务
  • DiscardOldestPolicy 会抛弃最旧的任务
  • 自定义拒绝策略

线程池常用方法

execute(Runnable)  //无返回值
submit(Runnable/Callable) // 有返回值

shutdown:平滑的关闭ExecutorService,不可以提交新的task,已经提交的将继续执行(包含提交正在执行和提交未执行)
          当所有已提交任务执行完毕,线程池即被关闭。
          提交使用拒绝策略处理。
awaitTermination:当等待超过设定时间时,会监测ExecutorService是否已经关闭,
         若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用 
         接收timeout和unit两个参数,用于设定超时时间及单位。
shutdownNow :
(1)线程池的状态立刻变成STOP状态,此时不能再往线程池中添加新的任务。
(2)终止等待执行的线程,并返回它们的列表;
(3)试图停止所有正在执行的线程,试图终止的方法是调用Thread.interrupt(),但是大家知道,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。

线程池数量设置

cpu密集型:核心线程数 = cpu(处理器)数量

Io密集型:核心线程数 = cpu(处理器)数量 * 2

其他线程池

线程池主要分两类,ThreadPoolExecutor和ForkJoinPool;使用较多的是ThreadPoolExecutor,也可以通过Executors来创建需要的线程池;

线程池解决了两个问题: 一是在执行大量的异步任务时,因为线程池减少了任务开始前的准备工作,如频繁创建线程,启动线程等工作,提升了性能表现;二是提供了一种绑定资源和管理资源的途径,可以进行一些基础的统计分析,比如已经完成的任务数量等。

任务调度线程池

ScheduledExecutorService使用示例

@Slf4j
public class ScheduledThreadPoolDemo implements Runnable{

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //两种创建方式,都是一个代码
        //ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(10);
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);

        //使用ScheduledThreadPool来延迟执行Callable或者Runnable,返回一个对象可用来取消或者查询任务执行情况
        //ScheduledFuture future = scheduledExecutorService.schedule(Callable<V> callable, long delay, TimeUnit unit);
//        Callable c1 = new CallableDemo("A"); RunnableDemo r1 = new RunnableDemo();
//        ScheduledFuture future = scheduledExecutorService.schedule(c1, 3, TimeUnit.SECONDS);
//        future.isDone();

         //循环执行任务,在等待initialDelay.unit后执行,然后每隔period.unit后实行;同样返回ScheduledFuture对象;
        //如果此任务的任何一个执行要花费比其周期更长的时间,则将推迟(一定时间)后续执行,但不会同时执行。周期为4秒,执行耗时3秒,每次开始打印间隔一秒;
        //如果任务的任何一个执行遇到异常,则后续执行都会被取消。否则,只能通过执行程序的取消或终止方法来终止该任务。
        //scheduledExecutorService.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
        ScheduledThreadPoolDemo scheduledThreadPoolDemo = new ScheduledThreadPoolDemo();
        //scheduledExecutorService.scheduleAtFixedRate(scheduledThreadPoolDemo, 5, 4, TimeUnit.SECONDS);

        //和上一个方法差不多,但是会等上一个任务结束,然后再等待delay时间去执行
        //scheduledExecutorService.scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
        scheduledExecutorService.scheduleWithFixedDelay(scheduledThreadPoolDemo, 5, 4, TimeUnit.SECONDS);
    }

    @Override
    public void run() {
        log.info("----执行方法开始---");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("----执行方法结束---");
    }
}

工作窃取线程池 ForkJoinPool

ForkJoin并发框架:Fork=分解 + Join=合并

ForkJoinPool:ForkJoin线程池,实现了ExecutorService接口和工作窃取算法,用于线程调度与管理。

ForkJoinTask:ForkJoin任务,提供了fork()方法和join()方法。通常不直接使用,而是使用以下子类: RecursiveAction:无返回值的任务,通常用于只fork不join的情形。RecursiveTask:有返回值的任务,通常用于fork+join的情形。

根据ForkJoinTask的两种类型,可以将ForkJoin并发框架划分为两种用法:

only fork:递归划分子任务,分别执行,但是并不需要合并各自的执行结果。

fork+join:递归划分子任务,分别执行,然后递归合并计算结果。

参考转载文章:https://blog.csdn.net/hanchao5272/article/details/79982095

public class TestForkJoinPool {

    public static void main(String[] args) {
        MyTask mt = new MyTask(1);

        // Executors和new ForkJoinPool实现是一样的,默认情况下Runtime.getRuntime().availableProcessors()得到线程数
        //ExecutorService executorServiceFk = Executors.newWorkStealingPool();
        ForkJoinPool forkJoinPool = new ForkJoinPool(10);
        Future<Integer> result = forkJoinPool.submit(mt);
        try {
            System.out.println(result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        forkJoinPool.shutdown();
    }

    public static class MyTask extends RecursiveTask<Integer> {
        int i;
        public MyTask(int i) {
            this.i = i;
        }
        @Override
        protected Integer compute() {
            if (i >= 100) {
                return i * i;
            }

            MyTask newTask2 = new MyTask(i + 1);
            newTask2.fork();
            return i*i + newTask2.join();
        }
    }
}
发布了62 篇原创文章 · 获赞 33 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/Zzhou1990/article/details/95618873