Java并发JUC线程池

文章目录

Java并发JUC线程池

为什么要有线程池?

线程池能够对线程进行统一分配,调优和监控:
1、降低资源消耗(线程无限制地创建,然后使用完毕后销毁)
2、提高响应速度(无须创建线程)
3、提高线程的可管理性

Java是实现和管理线程池有哪些方式?请简单举例如何使用

从JDK 5开始,把工作单元与执行机制分离开来,工作单元包括Runnable和Callable,而执行机制由Executor框架提供。
下面是一个例子:

工作线程WorkerThread

public class WorkerThread implements Runnable {
    private String command;
    public WorkerThread(String s){
        this.command = s;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+" Start. Command = " + command);
        processCommand();
        System.out.println(Thread.currentThread().getName() + " End.");
    }
    private void processCommand() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public String toString(){
        return this.command;
    }
}

线程池SimpleThreadPool

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleThreadPool {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            Runnable worker = new WorkerThread("" + i);
            executor.execute(worker);
          }
        executor.shutdown(); // This will make the executor accept no new threads and finish all existing threads in the queue
        while (!executor.isTerminated()) { // Wait until all threads are finish,and also you can use "executor.awaitTermination();" to wait
        }
        System.out.println("Finished all threads");
    }
}
// 创建了固定大小为五个工作线程的线程池。然后分配给线程池十个工作,因为线程池大小为五,它将启动五个工作线程先处理五个工作,其他的工作则处于等待状态,一旦有工作完成,空闲下来工作线程就会捡取等待队列里的其他工作进行执行。
// 程序的输出如下:
pool-1-thread-2 Start. Command = 1
pool-1-thread-4 Start. Command = 3
pool-1-thread-1 Start. Command = 0
pool-1-thread-3 Start. Command = 2
pool-1-thread-5 Start. Command = 4
pool-1-thread-4 End.
pool-1-thread-5 End.
pool-1-thread-1 End.
pool-1-thread-3 End.
pool-1-thread-3 Start. Command = 8
pool-1-thread-2 End.
pool-1-thread-2 Start. Command = 9
pool-1-thread-1 Start. Command = 7
pool-1-thread-5 Start. Command = 6
pool-1-thread-4 Start. Command = 5
pool-1-thread-2 End.
pool-1-thread-4 End.
pool-1-thread-3 End.
pool-1-thread-5 End.
pool-1-thread-1 End.
Finished all threads

输出表明线程池中至始至终只有五个名为"pool-1-thread-1"到"pool-1-thread-5" 的五个线程,这五个线程不随着工作的完成而消亡,会一直存在,并负责执行分配给线程池的任务,直到线程池消亡。
上面的例子在创建线程池的时候,使用了Executors工具类提供的ThreadPoolExecutor的简单的ExecutorService类的实现,但是ThreadPoolExecutor提供的功能远不止于此。

可以在创建ThreadPoolExecutor实例时指定活动线程的数量,也可以限制线程池的大小并且创建我们自己的RejectedExecutionHandler实现来处理不能适应工作队列的工作。
自定义的RejectedExecutionHandler接口的实现

import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
public class RejectedExecutionHandlerImpl implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println(r.toString() + " is rejected");
    }
}

ThreadPoolExecutor提供了一些方法,我们可以使用这些方法来查询executor的当前状态,线程池大小,活动线程数量以及任务数量。
因此可以用来一个监控线程在特定的时间间隔内打印executor 信息。

import java.util.concurrent.ThreadPoolExecutor;
public class MyMonitorThread implements Runnable
{
    private ThreadPoolExecutor executor;
    private int seconds;
    private boolean run=true;
    public MyMonitorThread(ThreadPoolExecutor executor, int delay)
    {
        this.executor = executor;
        this.seconds=delay;
    }
    public void shutdown(){
        this.run=false;
    }
    @Override
    public void run()
    {
        while(run){
            System.out.println(
                String.format("[monitor] [%d/%d] Active: %d, Completed: %d, Task: %d, isShutdown: %s, isTerminated: %s",
                    this.executor.getPoolSize(),
                    this.executor.getCorePoolSize(),
                    this.executor.getActiveCount(),
                    this.executor.getCompletedTaskCount(),
                    this.executor.getTaskCount(),
                    this.executor.isShutdown(),
                    this.executor.isTerminated()));
            try {
                Thread.sleep(seconds*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

使用ThreadPoolExecutor的线程池实现例子

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class WorkerPool {
    public static void main(String args[]) throws InterruptedException{
        //RejectedExecutionHandler implementation
        RejectedExecutionHandlerImpl rejectionHandler = new RejectedExecutionHandlerImpl();
        //Get the ThreadFactory implementation to use
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        //creating the ThreadPoolExecutor
        ThreadPoolExecutor executorPool = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2), threadFactory, rejectionHandler);
        //start the monitoring thread
        MyMonitorThread monitor = new MyMonitorThread(executorPool, 3);
        Thread monitorThread = new Thread(monitor);
        monitorThread.start();
        //submit work to the thread pool
        for(int i=0; i<10; i++){
            executorPool.execute(new WorkerThread("cmd"+i));
        }
        Thread.sleep(30000);
        //shut down the pool
        executorPool.shutdown();
        //shut down the monitor thread
        Thread.sleep(5000);
        monitor.shutdown();
    }
}
// 输出的日志
pool-1-thread-1 Start. Command = cmd0
pool-1-thread-4 Start. Command = cmd5
cmd6 is rejected
pool-1-thread-3 Start. Command = cmd4
pool-1-thread-2 Start. Command = cmd1
cmd7 is rejected
cmd8 is rejected
cmd9 is rejected
[monitor] [0/2] Active: 4, Completed: 0, Task: 6, isShutdown: false, isTerminated: false
[monitor] [4/2] Active: 4, Completed: 0, Task: 6, isShutdown: false, isTerminated: false
pool-1-thread-4 End.
pool-1-thread-1 End.
pool-1-thread-2 End.
pool-1-thread-3 End.
pool-1-thread-1 Start. Command = cmd3
pool-1-thread-4 Start. Command = cmd2
[monitor] [4/2] Active: 2, Completed: 4, Task: 6, isShutdown: false, isTerminated: false
[monitor] [4/2] Active: 2, Completed: 4, Task: 6, isShutdown: false, isTerminated: false
pool-1-thread-1 End.
pool-1-thread-4 End.
[monitor] [4/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [0/2] Active: 0, Completed: 6, Task: 6, isShutdown: true, isTerminated: true
[monitor] [0/2] Active: 0, Completed: 6, Task: 6, isShutdown: true, isTerminated: true

注意:上面的例子中,初始化ThreadPoolExecutor时,我们保持初始池大小为2,最大池大小为4而工作队列大小为2。因此如果已经有4个正在执行的任务而此时分配来更多任务的话,工作队列将仅仅保留他们(新任务)中的2个,其他的将会被RejectedExecutionHandlerImpl处理。上面的输出日志可以证实以上观点。

ThreadPoolExecutor的原理?

其实java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。
在这里插入图片描述
当一个任务提交至线程池之后:
1、线程池首先判断当前运行的线程数量是否少于corePoolSize。如果是,则创建一个新的工作线程来执行任务。如果都在执行任务,则进入2;
2、判断BlockingQueue是否已经满了,倘若还没有满,则将线程放入BlockingQueue。否则进入3;
3、如果创建一个新的工作线程将使当前运行的线程数量超过maximumPoolSize,则交给RejectedExecutionHandler来处理任务。

ThreadPoolExecutor有哪些核心的配置参数?请简要说明

构造函数如下:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
                              TimeUnit unit, BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler)

corePoolSize
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize,即使有其他空闲线程能够执行新来的任务, 也会继续创建线程;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;当阻塞队列是无界队列,则maximumPoolSize则不起作用,因为会一直持续地放入workQueue中;
keepAliveTime
线程空闲时的存活时间,即当线程没有任务执行时,该线程继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用,超过这个时间的空闲线程将被终止;
unit
keepAliveTime的单位;
workQueue
用来保存等待被执行的任务的阻塞队列,在JDK中提供了如下阻塞队列:
ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
LinkedBlockingQueue:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQueue;
SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue;
PriorityBlockingQueue:具有优先级的无界阻塞队列;
threadFactory
创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。默认为DefaultThreadFactory;
handler
线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
AbortPolicy:直接抛出异常,默认策略;
CallerRunsPolicy:用调用者所在的线程来执行任务;
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
DiscardPolicy:直接丢弃任务;
自定义ThreadFactory

class CustomThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
    CustomThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" + poolNumber.getAndIncrement() + "-customthread-";
    }
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement()+"=zlj", 0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        ExecutorService threadPoolExecutor = new ThreadPoolExecutor(2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<Runnable>(3),
                new CustomThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
                );
        for (int i=0;i<6;i++) {
            threadPoolExecutor.execute(()-> {
                System.out.println(Thread.currentThread().getName() + "==正在处理业务");
            });
        }
    }
}

自定义RejectedExecutionHandler

public class MyRejectPolicy implements RejectedExecutionHandler{
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println("自定义拒绝策略.....");
        System.out.println("当前被拒绝的任务------" + Thread.currentThread().getName() + ": " + r.toString());
    }
}

ThreadPoolExecutor可以创建哪是哪三种线程池呢?

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>());
}

线程池的线程数量达corePoolSize后,即使线程池没有可执行任务时,也不会释放线程。FixedThreadPool的工作队列为无界队列LinkedBlockingQueue(队列容量为Integer.MAX_VALUE),这会导致以下问题:
1、线程池里的线程数量不超过corePoolSize,这导致了maximumPoolSize和keepAliveTime将会是个无用参数。
2、由于使用了无界队列,所以FixedThreadPool永远不会拒绝,即饱和策略失效。

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

初始化的线程池中只有一个线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行。
由于使用了无界队列, 所以SingleThreadPool永远不会拒绝,即饱和策略失效。

newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                    60L, TimeUnit.SECONDS,
                                    new SynchronousQueue<Runnable>());
}

线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列;和newFixedThreadPool创建的线程池不同,newCachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;执行过程与前两种稍微不同:
1、主线程调用SynchronousQueue的offer()方法放入task,倘若此时线程池中有空闲的线程尝试读取 SynchronousQueue的task,即调用了SynchronousQueue的poll(),那么主线程将该task交给空闲线程,否则执行步骤2。
2、当线程池为空或者没有空闲的线程,则创建新的线程执行任务。
3、执行完任务的线程倘若在60s内仍空闲,则会被终止。因此长时间空闲的CachedThreadPool不会持有任何线程资源。

当队列满了并且worker的数量达到maxSize的时候,会怎么样?

当队列满了并且worker的数量达到maxSize的时候,执行具体的拒绝策略(可以默认也可以自定义)。

private volatile RejectedExecutionHandler handler;

说说ThreadPoolExecutor有哪些RejectedExecutionHandler策略?默认是什么策略?

AbortPolicy, 默认
该策略是线程池的默认策略。使用该策略时,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。源码如下:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
  //不做任何处理,直接抛出异常
 throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " + e.toString());
}

DiscardPolicy
这个策略和AbortPolicy的slient版本,如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常。源码如下:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    //就是一个空的方法
}

DiscardOldestPolicy
这个策略从字面上也很好理解,丢弃最老的。也就是说如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列。 因为队列是队尾进,队头出,所以队头元素是最老的,因此每次都是移除对头元素后再尝试入队。源码如下:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        //移除队头元素
        e.getQueue().poll();
        //再尝试入队
        e.execute(r);
    }
}

CallerRunsPolicy
使用此策略,如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行。就像是个急脾气的人,我等不到别人来做这件事就干脆自己干。源码如下:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        //直接执行run方法
        r.run();
    }
}

简要说下线程池的任务执行机制(ThreadPoolExecutor.execute(Runnable command))?

execute –> addWorker –>runworker (getTask)
1、线程池的工作线程通过Woker类实现,在ReentrantLock锁的保证下,把Woker实例插入到HashSet后,并启动Woker中的线程。
2、从Woker类的构造方法实现可以发现:Worker类包含了
Runnable firstTask;
final Thread thread;
线程工厂在创建线程thread时,将Woker实例本身this作为参数传入,当thread执行start方法启动线程thread时,本质是执行了Worker的runWorker方法。
3、firstTask执行完成之后,通过getTask方法从阻塞队列中获取等待的任务,如果队列中没有任务,getTask方法会被阻塞并挂起,不会占用cpu资源;

线程池中任务是如何提交的(ThreadPoolExecutor.submit(Callable task)?

在这里插入图片描述
1、submit任务,等待线程池execute;
2、执行FutureTask类的get方法时,会把主线程封装成WaitNode节点并保存在waiters链表中,并阻塞等待运行结果;
3、FutureTask任务执行完成后,通过UNSAFE设置waiters相应的waitNode为null,并通过LockSupport类unpark方法唤醒主线程;
单个任务结果获取方式

public class Test{
    public static void main(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();
        Future<String> future = es.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "future result";
            }
        });
        try {
            String result = future.get();
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

多个任务结果获取方式

public class TestFuture2 {
    public static void main(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();
        int taskNum = 100;
        List<Future<String>> pointTaskFutureList = new ArrayList<>(taskNum);
        for (int i = 0; i < taskNum; i++) {
            Future<String> future = es.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return "future result " + Thread.currentThread().getName();
                }
            });
            pointTaskFutureList.add(future);
        }
        try {
            for (int i = 0; i < taskNum; i++) {
                String result = pointTaskFutureList.get(i).get();
                System.out.println(result);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        es.shutdown();
    }
}

在实际业务场景中,Future和Callable基本是成对出现的,Callable负责产生结果,Future负责获取结果。Callable接口类似于Runnable,只是Runnable没有返回值。Callable任务除了返回正常结果之外,如果发生异常,该异常也会被返回,即Future可以拿到异步执行任务各种结果;Future.get方法会导致主线程阻塞,直到Callable任务执行完成;

线程池中任务是如何关闭的?

shutdown
将线程池里的线程状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
shutdownNow
将线程池里的线程状态设置成STOP状态,然后停止所有正在执行或暂停任务的线程。
只要调用这两个关闭方法中的任意一个,isShutDown() 返回true;当所有任务都成功关闭了, isTerminated()返回true

在配置线程池的时候需要考虑哪些配置因素?

任务的优先级任务的执行时间长短任务的性质(CPU密集/ IO密集)任务的依赖关系这四个角度来分析。并且近可能地使用有界的工作队列
性质不同的任务可用使用不同规模的线程池分开处理:
CPU密集型: 尽可能少的线程,Ncpu+1;
IO密集型: 尽可能多的线程,Ncpu*2,比如数据库连接池;
混合型:CPU密集型的任务与IO密集型任务的执行时间差别较小,拆分为两个线程池;否则没有必要拆分。

如何监控线程池的状态?

可以使用ThreadPoolExecutor以下方法:
getTaskCount():Returns the approximate total number of tasks that have ever been scheduled for execution。
getCompletedTaskCount():Returns the approximate total number of tasks that have completed execution。返回结果少于getTaskCount()。
getLargestPoolSize():Returns the largest number of threads that have ever simultaneously been in the pool。返回结果小于等于maximumPoolSize。
getPoolSize():Returns the current number of threads in the pool。
getActiveCount():Returns the approximate number of threads that are actively executing tasks。

为什么很多公司不允许使用Executors去创建线程池?那么推荐怎么使用呢?

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写代码的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明下Executors各个方法的弊端:
newFixedThreadPool和newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,存在OOM的风险。
newCachedThreadPool和newScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,存在OOM的风险。
推荐方式1:首先引入commons-lang3包

ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
        new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());

推荐方式2:首先引入com.google.guava包

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
// excute
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
//gracefully shutdown
pool.shutdown();

推荐方式3:SpringBoot使用注解

@Configuration
@EnableAsync // 允许使用异步方法
public class ThreadPoolConfig {
    @Bean
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        threadPoolTaskExecutor.setCorePoolSize(5);
        // 设置最大线程数
        threadPoolTaskExecutor.setMaxPoolSize(5);
        // 设置工作队列大小
        threadPoolTaskExecutor.setQueueCapacity(2000);
        // 设置线程名称前缀
        threadPoolTaskExecutor.setThreadNamePrefix("threadPoolTaskExecutor-->");
        // 设置拒绝策略.当工作队列已满,线程数为最大线程数的时候,接收新任务抛出RejectedExecutionException异常
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        // 初始化线程池
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }
}

@Service
public class HelloService {
    Logger logger = LoggerFactory.getLogger(HelloService.class);
     /**
     * @Async标注的方法,称之为异步方法;这些方法将在执行的时候,
     * 将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。
     */
    @Async // 使用异步方法
    public void sayHello() {
        logger.info("start say hello");
        System.out.println(Thread.currentThread().getName());
        System.out.println("hello");
        logger.info("end say hello");
    }
}

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ThreadPoolApplication.class)
public class HelloServiceTest {
    @Autowired
    HelloService helloService;
    @Test
    public void testSayHello() throws Exception {
        helloService.sayHello();
    }
} 

参考文章链接:https://blog.csdn.net/qq_24983911/article/details/94722569

ScheduledThreadPoolExecutor要解决什么样的问题?

在很多业务场景中,我们可能需要周期性的运行某项任务来获取结果,比如周期数据统计,定时发送数据等。在并发包出现之前,Java 早在1.3就提供了Timer类(只需要了解,目前已渐渐被ScheduledThreadPoolExecutor代替)来适应这些业务场景。随着业务量的不断增大,我们可能需要多个工作线程运行任务来尽可能的增加产品性能,或者是需要更高的灵活性来控制和监控这些周期业务。这些都是ScheduledThreadPoolExecutor诞生的必然性。

ScheduledThreadPoolExecutor的运行机制?

在这里插入图片描述
DelayQueue是一个无界队列,所以ThreadPoolExecutor的maximumPoolSize在ScheduledThreadPoolExecutor中没有什么意义(设置maximumPoolSize的大小没有什么效果)。
ScheduledThreadPoolExecutor的执行主要分为两大部分。
1)当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时,会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFutur接口的ScheduledFutureTask。
2)线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。

ScheduledThreadPoolExecutor中的线程执行某个周期任务的4个步骤

在这里插入图片描述
1)线程1从DelayQueue中获取已到期的ScheduledFutureTask(DelayQueue.take());到期任务是指ScheduledFutureTask的time大于等于当前时间。
2)线程1执行这个ScheduledFutureTask。
3)线程1修改ScheduledFutureTask的time变量为下次将要被执行的时间。
4)线程1把这个修改time之后的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。

使用例子

①、schedule(Runnable command, long delay, TimeUnit unit)
创建并执行在给定延迟后启用的一次性操作

package ThreadPool;

import java.sql.Time;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
 * @author czd
 */
public class ScheduledThreadPoolExecutorTest {
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(2);
       //1、schedule(Runnable command, long delay, TimeUnit unit)
        ScheduledFuture future = scheduledThreadPoolExecutor.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("当前时间:" + System.currentTimeMillis());
            }
        }, 2, TimeUnit.SECONDS);
        //有兴趣可以把下面两行代码的注释去掉玩玩看
        //cancel(true):是取消延迟执行
        //future.cancel(true);
    }
}

②、scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay后开始执行,然后在initialDelay + period后执行,接着在initialDelay + 2 * period后执行,依此类推。
scheduleAtFixedRate总结:
1、当执行时间 > 频率时间时 在程序结束本次执行后,会立即进行下一次的执行。
2、当执行时间 < 频率时间时 在程序结束本次执行后,会等到频率时间到了再去执行下一次。

package ThreadPool;

import java.sql.Time;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
 * @author czd
 */
public class ScheduledThreadPoolExecutorTest {
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(2);
        //2、scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
        scheduledThreadPoolExecutor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("当前时间:" + System.currentTimeMillis() + " 我是周期性执行!");
            }
        }, 2, 5, TimeUnit.SECONDS);
    }
}

③、scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。
scheduleWithFixedDelay总结:
程序确实是在上一次执行完毕之后5s钟再次执行,也就是说scheduleWithFixedDelay是在上一次线程执行结束之后的多长时间执行(在这个示例就是一共2+5执行)。

package ThreadPool;

import java.sql.Time;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
 * @author czd
 */
public class ScheduledThreadPoolExecutorTest {
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(2);
        //3、scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
        scheduledThreadPoolExecutor.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("当前时间:" + System.currentTimeMillis() + " 我是周期性执行!");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 2 ,5, TimeUnit.SECONDS);
    }
}

为什么ThreadPoolExecutor的调整策略却不适用于ScheduledThreadPoolExecutor?

例如: 由于ScheduledThreadPoolExecutor是一个固定核心线程数大小的线程池,并且使用了一个无界队列,所以调整maximumPoolSize对其没有任何影响(所以ScheduledThreadPoolExecutor没有提供可以调整最大线程数的构造函数,默认最大线程数固定为Integer.MAX_VALUE)。此外,设置corePoolSize为0或者设置核心线程空闲后清除(allowCoreThreadTimeOut)同样也不是一个好的策略,因为一旦周期任务到达某一次运行周期时,可能导致线程池内没有线程去处理这些任务。

Executors提供了几种方法来构造ScheduledThreadPoolExecutor?

newScheduledThreadPool:可指定核心线程数的线程池。
newSingleThreadScheduledExecutor:只有一个工作线程的线程池。如果内部工作线程由于执行周期任务异常而被终止,则会新建一个线程替代它的位置。

具体阐述Fork/Join的分治思想和work-stealing 实现方式?

分治算法(Divide-and-Conquer)

分治算法(Divide-and-Conquer)把任务递归的拆分为各个子任务,这样可以更好的利用系统资源,尽可能的使用所有可用的计算能力来提升应用性能。
首先看一下Fork/Join框架的任务运行机制:
在这里插入图片描述
例子,用ForkJoin方式实现1+2+3+…+100000?

public class Test {
	static final class SumTask extends RecursiveTask<Integer> {
		private static final long serialVersionUID = 1L;
		final int start; //开始计算的数
		final int end; //最后计算的数
		SumTask(int start, int end) {
			this.start = start;
			this.end = end;
		}
		@Override
		protected Integer compute() {
			//如果计算量小于1000,那么分配一个线程执行if中的代码块,并返回执行结果
			if(end - start < 1000) {
				System.out.println(Thread.currentThread().getName() + " 开始执行: " + start + "-" + end);
				int sum = 0;
				for(int i = start; i <= end; i++)
					sum += i;
				return sum;
			}
			//如果计算量大于1000,那么拆分为两个任务
			SumTask task1 = new SumTask(start, (start + end) / 2);
			SumTask task2 = new SumTask((start + end) / 2 + 1, end);
			//执行任务
			task1.fork();
			task2.fork();
			//获取任务执行的结果
			return task1.join() + task2.join();
		}
	}
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ForkJoinPool pool = new ForkJoinPool();
		ForkJoinTask<Integer> task = new SumTask(1, 10000);
		pool.submit(task);
		System.out.println(task.get());
	}
}
执行结果:
ForkJoinPool-1-worker-1 开始执行: 1-625
ForkJoinPool-1-worker-7 开始执行: 6251-6875
ForkJoinPool-1-worker-6 开始执行: 5626-6250
ForkJoinPool-1-worker-10 开始执行: 3751-4375
ForkJoinPool-1-worker-13 开始执行: 2501-3125
ForkJoinPool-1-worker-8 开始执行: 626-1250
ForkJoinPool-1-worker-11 开始执行: 5001-5625
ForkJoinPool-1-worker-3 开始执行: 7501-8125
ForkJoinPool-1-worker-14 开始执行: 1251-1875
ForkJoinPool-1-worker-4 开始执行: 9376-10000
ForkJoinPool-1-worker-8 开始执行: 8126-8750
ForkJoinPool-1-worker-0 开始执行: 1876-2500
ForkJoinPool-1-worker-12 开始执行: 4376-5000
ForkJoinPool-1-worker-5 开始执行: 8751-9375
ForkJoinPool-1-worker-7 开始执行: 6876-7500
ForkJoinPool-1-worker-1 开始执行: 3126-3750
50005000

如何使用Executors工具类创建ForkJoinPool?

// parallelism定义并行级别
public static ExecutorService newWorkStealingPool(int parallelism);
// 默认并行级别为JVM可用的处理器个数,即Runtime.getRuntime().availableProcessors()
public static ExecutorService newWorkStealingPool();

Fork/Join在使用时有哪些注意事项?结合JDK中的斐波那契数列实例具体说明

斐波那契数列: 1、1、2、3、5、8、13、21、34、…… 公式:F(1)=1,F(2)=1,F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)

public static void main(String[] args) {
    ForkJoinPool forkJoinPool = new ForkJoinPool(4); // 最大并发数4
    Fibonacci fibonacci = new Fibonacci(20);
    long startTime = System.currentTimeMillis();
    Integer result = forkJoinPool.invoke(fibonacci);
    long endTime = System.currentTimeMillis();
    System.out.println("Fork/join sum: " + result + " in " + (endTime - startTime) + " ms.");
}
//以下为官方API文档示例
static  class Fibonacci extends RecursiveTask<Integer> {
    final int n;
    Fibonacci(int n) {
        this.n = n;
    }
    @Override
    protected Integer compute() {
        if (n <= 1) {
            return n;
        }
        Fibonacci f1 = new Fibonacci(n - 1);
        f1.fork(); 
        Fibonacci f2 = new Fibonacci(n - 2);
        return f2.compute() + f1.join(); 
    }
}

多个递归方式

public class ForkJoin extends RecursiveTask<Integer> {
    final int n;
    ForkJoin(int n) {
        this.n = n;
    }
    @Override
    protected Integer compute() {
        // 设置递归的出口
        if (n == 0) {            return 0;        }
        if (n == 1) {            return 1;        }
        if (n == 2) {            return 1;        }
        // 要注意的是两个任务都fork的情况,必须按照f1.fork(),f2.fork(), f2.join(),f1.join()这样的顺序,不然有性能问题,详见上面注意事项中的说明。
        /*ForkJoin f1 = new ForkJoin(n - 1);
        f1.fork();
        ForkJoin f2 = new ForkJoin(n - 2);
        f2.fork();
        int result = f2.join() + f1.join();
        System.out.println("当前线程号为:" + Thread.currentThread().getName() + " 传入的N为:" + n + " 计算结果为:" + result);
        return result;
*/
        // f2当作主线进行计算,f1通过其他线程进行计算
        /*ForkJoin f1 = new ForkJoin(n - 1);
        f1.fork();
        ForkJoin f2 = new ForkJoin(n - 2);
        int result = f2.compute() + f1.join();
        System.out.println("当前线程号为:" + Thread.currentThread().getName() + " 传入的N为:" + n + " 计算结果为:" + result);
        return result;*/

        // 通过单线程的递归计算方法
        ForkJoin f1 = new ForkJoin(n - 1);
        ForkJoin f2 = new ForkJoin(n - 2);
        int result = f2.compute() + f1.compute();
        System.out.println("当前线程号为:" + Thread.currentThread().getName() + " 传入的N为:" + n + " 计算结果为:" + result);
        return result;
    }
    
    public static void main(String[] args) {
        System.out.println("当前线程号为:" + Thread.currentThread().getName());
        ForkJoinPool forkJoinPool = new ForkJoinPool(4); // 最大并发数4
        ForkJoin fibonacci = new ForkJoin(20);
        long startTime = System.currentTimeMillis();
        Integer result = forkJoinPool.invoke(fibonacci);
        long endTime = System.currentTimeMillis();
        System.out.println("Fork/join sum: " + result + " in " + (endTime - startTime) + " ms.");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值