多线程之线程池

写在前面

本文一起看下线程池相关的内容。

1:主要的类

如下:

在这里插入图片描述

接下来看下每个类。

1.1:Executor

权限定名称java.util.concurrent.Executor,顶层接口,定义了最核心的任务执行的操作,源码如下:

package java.util.concurrent;

public interface Executor {

    void execute(Runnable command);
}

只有一个无返回值的execute方法,提供最基础的任务执行功能,但捕获不到异常。

1.2:ExecutorService

Executor接口的子接口,定义了任务执行相关更全面的方法,如下:

package java.util.concurrent;
import java.util.List;
import java.util.Collection;

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
  • shutdown()
    停止线程池,会等待正在执行的任务执行完毕后再停止,优雅停机可使用该方法。不同于shutdownNow立即停止。
  • shutdownNow()
    立即停止线程池,就算是有没有执行完毕的任务也会停止。不同于shutdown方法会等待正在执行的任务执行完毕再停止。
  • awaitTermination()
    检测是否还存在没有执行完毕的任务,优雅停机场景中可以通过该方法检测是否所有的任务都执行完毕。
  • submit()
    返回Future,可以通过其get方法获取任务执行的结果,且可以获取到内部任务执行发生的异常。

1.3:ThreadFactory

创建线程的工厂类的顶层接口,通过实现该接口来来提供创建线程的逻辑,源码如下:

public interface ThreadFactory {
    // 创建线程,并直接指定一个线程要执行的任务,创建出来直接干活
    Thread newThread(Runnable r);
}

可能实现代码如下:

在这里插入图片描述

1.4:ThreadPoolExecutor

基于线程池执行任务的类,以execute方法为例看下其工作过程:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    // 获取当前正在执行的任务数
    int c = ctl.get();
    // 1
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 2
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 3
    else if (!addWorker(command, false))
        reject(command);
}

1处代码是判断如果是当前正在执行的任务数小于核心线程数corePoolSize,则直接创建线程执行任务。2处代码判断是否能够添加任务到任务队列中。3处判断线程数是否达到最大线程数,如果没有则增加新的线程来执行任务,否则直接执行拒绝策略。通过流程图方式展示如下:

在这里插入图片描述

这里注意,当线程数达到corePoolSize是并没有直接创建线程来执行任务,而是将任务添加到任务队列中,这么做的原因主要有2个,第一个是创建线程对象本身的操作因为涉及到底层操作,所以比较重,时间可能比较慢,第2是corePoolSize个的线程可能很快就能执行完毕当前的任务,也就能很快的消费任务队列中的任务了。如果是任务队列满了,说明corePoolSize个线程执行压力确实比较大了,此时才会创建新的干活的线程。

1.4.1:线程池的主要参数

  • 任务队列
    阻塞任务队列,一个线程放数据,一个线程取数据,当放数据线程在队列满时阻塞,当取数据线程在队列空时阻塞,有如下四种实现方式:
1:ArrayBlockingQueue
    底层数组,需要指定大小,FIFO结构
2:LinkedBlockingQueue
    底层链表,不需要指定大小,内部最大大小为Integer.MAX_VALUE,相当于不指定大小,需要避免堆内存溢出,FIFO结构。
3:PriorityBlockingQueue
    带有优先级的阻塞对列,通过指定类似于score,rank类的值来指定优先级,底层通过,非FIFO结构,优先级确定其顺序。
4:SynchronizedQueue
    特殊的阻塞队列,单放单取,放数据线程放数据,存数据线程存数据,...,如此重复!!!
  • 拒绝策略
    接口是java.util.concurrent.RejectedExecutionHandler,源码如下:
package java.util.concurrent;

public interface RejectedExecutionHandler {
    // 参数是当前执行拒绝策略的任务,和线程池执行对象
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
  • 1:AbortPolicy
    丢弃策略,丢弃任务,并抛出RjectedExecutionException。
  • 2:DiscardPolicy
    抛弃策略,直接抛弃,也不抛出异常,因此应用程序本身将无法感知。
  • 3:DiscardOldedPolicy
    丢弃最老的任务,然后尝试提交。
  • 4:CallerRunsPolicy
    由当前申请执行任务的线程执行任务。一般这种使用的比较多。

1.4.2:程序实例

在这里插入图片描述

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

创建线程池比较简单的方式是使用Executors工具类,提供主要工具方式如下:

  • newSingledThreadPoolExecutor
    只有一个线程执行任务,此时串行执行所有任务,能够保证按照加入任务提交的顺序来提交任务,如果这个唯一线程因为某些异常终止了会创建一个新的线程来替代。此时其实就是创建了corePoolSize和maxPoolSize大小都是1的线程池,源码如下:
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

实例代码:

public static void main(String[] args) {

    ExecutorService executorService = Executors.newSingleThreadExecutor();

    for (int i = 0; i < 10; i++) {
        final int no = i;
        executorService.execute(() -> {
            System.out.println("start:" + no);
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end:" + no);
        });
    }
    // 停止线程池(任务执行完毕后),释放线程资源
    executorService.shutdown();
    System.out.println("Main Thread End!");
}

运行:

Main Thread End!
start:0
end:0
start:1
end:1
start:2
end:2
start:3
end:3
start:4
end:4
start:5
end:5
start:6
end:6
start:7
end:7
start:8
end:8
start:9
end:9

运行结束。
  • newFixedThreadPool
    固定大小线程数的线程池,如果线程数是1,则可以认为是newSingledThreadPoolExecutor,如果是提交任务时没有达到固定线程数,则会创建新线程。如果是线程池中的某个线程异常终止,则会创建一个新的线程进行补位。此时其实就是创建了corePoolSize和maxPoolSize大小相同的线程池对象,源码如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>());
}

实例代码:

public static void main(String[] args) {

    ExecutorService executorService = Executors.newFixedThreadPool(16);
    for (int i = 0; i < 100; i++) {
        final int no = i;
        executorService.execute(() -> {
            try {
                System.out.println("start:" + no);
                Thread.sleep(1000L);
                System.out.println("end:" + no);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
    executorService.shutdown();
    System.out.println("Main Thread End!");
}

运行:

start:0
start:4
start:3
start:2
start:1
start:5
....
end:98
end:99

运行结束。

可以start和end并不是配对输出的,no的值也不是从大到小输出的,这是因为线程的执行是随机的,即16个线程的具体执行过程我们是无法控制的。

  • newCachedThreadPool
    根据任务的数量来动态的创建线程,本身不对maxPoolSize大小做限制,指定为Integer.MAX_VALUE,但也并非能够创建这么多线程,因为还是要受到操作系统和JVM的限制。默认线程空闲60则会回收,源码如下:
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                    60L, TimeUnit.SECONDS,
                                    new SynchronousQueue<Runnable>());
}

注意1:其中的maxPoolSize大小是Integer.MAX_VALUE,即线程池大小不限制,因此实际工作中要申请,避免出现线程数过多,导致频繁的线程上下文切换,导致性能问题。

注意2:任务队列使用的是SynchronousQueue,即认为不会使用到任务队列,但一旦使用到的话,则会立即出发拒绝策略,实际使用过程中要特别注意。

实例代码:

public static void main(String[] args) {

        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 10000; i++) {
            final int no = i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("start:" + no);
                        Thread.sleep(1000L);
                        System.out.println("end:" + no);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            executorService.execute(runnable);
        }
        executorService.shutdown();
        System.out.println("Main Thread End!");


    }
}

运行:

...
start:2239
start:2240
start:2241
...
end:9999
end:9971
end:9960
...
  • newScheduledThreadPool
    线程大小不限制的线程池,按照指定的执行周期定期执行任务,源码如下:
public ScheduledThreadPoolExecutor(int corePoolSize,
                                    ThreadFactory threadFactory) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
            new DelayedWorkQueue(), threadFactory);
}

注意这里的maxPoolSize是Integer.MAX_VALUE,即不限制,任务队列使用的是DelayedWorkQueue,即带有延迟的队列。

实例代码:

public static void main(String[] args) {
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(16);

    for (int i = 0; i < 100; i++) {
        final int no = i;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("start:" + no);
                    Thread.sleep(1000L);
                    System.out.println("end:" + no);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        // 10s后执行
        executorService.schedule(runnable, 10, TimeUnit.SECONDS);
    }
    executorService.shutdown();
    System.out.println("Main Thread End!");

}

运行:

Main Thread End!
以下的是10秒后执行的 -- 我自己加的
start:0
start:1
start:2
...

2:其它知识点

2.1:线程数设置多少

假定核数为N,一般经验如下:

1:如果是计算密集型应用,则设置N+1,其中的1作为替补线程
2:如果是IO密集型应用,则设置2N,2N+1

2.2:Callbale接口

提供了唯一的call方法,有返回值这单不同于Runnable的run,源码如下:

package java.util.concurrent;

@FunctionalInterface
public interface Callable<V> {
   
    V call() throws Exception;
}

2.3:Future接口

代表将来执行结果的接口,其get方法可以通过阻塞的方式来获取将来执行的结果,源码如下:

package java.util.concurrent;

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

在这里插入图片描述

写在后面

参考文章列表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值