java线程池介绍

线程池类关系图:

下面重点介绍下以下3个类:

类名

使用场景

ThreadPoolExecutor

通用的线程池

ScheduledThreadPoolExecutor

针对定时、周期性执行的任务的线程池

ForkJoinPool

针对计算性任务,利用任务拆分、并行执行、工作窃取等机制,提升处理性能

ThreadPoolExecutor

构造函数

参数说明:

参数名

说明

详解

corePoolSize

核心线程数

线程池保持的最少的线程数量。当有第一个任务时,才一次性创建corePoolSize个数的线程。

maximumPoolSize

最大线程数

只有当队列满时,才会新建线程;如果当前的线程数量已经达到maximumPoolSize且workQueue已满,则走handler

keepAliveTime

空闲时间

当超过核心线程数量,且线程空闲时间超过keepAliveTime时,销毁该线程

unit

时间单位

keepAliveTime的时间单位

workQueue

任务队列

threadFactory

线程工厂

handler

异常处理

workQueue说明

从队列添加/获取元素有几种不同的方法:

添加元素:

方法名

说明

boolean add(E e)

非阻塞,插入成功返回true;如果队列满了则抛出IllegalStateExceptio

boolwan offer(E e)

非阻塞,插入成功返回true,如果队列满了则返回false

void put(E e)

阻塞,当队列满时会阻塞

获取元素:

方法名

说明

E take()

阻塞,从队列头部获取并删除元素,如果队列为空,则阻塞直至有新元素出现

E poll(long timeout, TimeUnit unit)

阻塞/非阻塞,从队列头部获取并删除元素,如果队列为空,则最多阻塞timeout

SynchronousQueue

​ 没有容量,直接提交队列,是无缓存等待队列,当任务提交进来,它总是马上将任务提交给线程去执行,如果线程已经达到最大,则执行拒绝策略;所以使用SynchronousQueue阻塞队列一般要求maximumPoolSize为无界(无限大),避免线程拒绝执行操作。从源码中可以看到容量为0:

   //是否为空,直接返回的true
   public boolean isEmpty() {
        return true;
    }

    //队列大小为0
    public int size() {
        return 0;
    }

LinkedBlockingQueue

默认情况下,LinkedBlockingQueue是个无界的任务队列,默认值是Integer.MAX_VALUE,当然我们也可以指定队列的大小。从构造LinkedBlockingQueue源码中可以看出它的大小指定方式:

   //默认构造函数,大小为Integer最大
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

   //也可以指定大小
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

为了避免队列过大造成机器负载,或者内存泄漏,我们在使用的时候建议手动传一个队列的大小。内部分别使用了takeLock和putLock对并发进行控制,添加和删除操作不是互斥操作,可以同时进行,这样大大提供了吞吐量。源码中有定义这两个锁:

   //获取元素使用的锁
   private final ReentrantLock takeLock = new ReentrantLock();

   //加入元素使用的锁
   private final ReentrantLock putLock = new ReentrantLock();

  //获取元素时使用到takeLock锁
  public E peek() {
        if (count.get() == 0)
            return null;
        final ReentrantLock takeLock = this.takeLock;
       //加锁操作
        takeLock.lock();
        try {
            //获取元素
            Node<E> first = head.next;
            if (first == null)
                return null;
            else
                return first.item;
        } finally {
            //解锁
            takeLock.unlock();
        }
    }
    
    //添加元素到队列中使用putLock锁
    public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        final AtomicInteger count = this.count;
        if (count.get() == capacity)
            return false;
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        //加锁操作
        putLock.lock();
        try {
            //队列中存放的数据小于队列设置的值
            if (count.get() < capacity) {
                //添加元素
                enqueue(node);
                c = count.getAndIncrement();
                if (c + 1 < capacity)
                    notFull.signal();
            }
        } finally {
            //解锁
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        return c >= 0;
    }

ArrayBlockingQueue

可以理解为有界的队列,创建的时候必须要指定队列的大小,从源码可以看出构造的时候要传递值:

    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

DelayQueue

是一个延迟队列,无界、队列中每个元素都有过期时间,当从队列获取元素时,只有过期的元素才会出队,而队列头部是最早过期的元素,若是没有过期,则进行等待。利用这个特性,我们可以用来处理定时任务调用的场景,例如订单过期未支付自动取消,设置一个在队列中过期的时间,过期了后,再去查询订单的状态,若是没支付,则调用取消订单的方法。

//获取元素
public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
                //获取元素
                E first = q.peek();
                if (first == null)
                    //进入等待
                    available.await();
                else {
                    //获取过期时间
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0)
                        //小于等于0则过期,返回此元素
                        return q.poll();
                    first = null; 
                    if (leader != null)
                        available.await();
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            //设置还需要等待的时间
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

RejectedExecutionHandler说明

AbortPolicy

直接抛出RejectedExecutionException

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
                                             e.toString());
    }

DiscardPolicy

直接丢弃任务

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}

CallerRunsPolicy

立即执行任务

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
        r.run();
    }
}

DiscardOldestPolicy

删除最老的任务(任务头部的任务),再把当前任务加进去

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
        e.getQueue().poll();
        e.execute(r);
    }
}

ScheduledThreadPoolExecutor

任务通过SeheduleFutureTask包装,存到DelayedWorkQueue保存,每次获取到时间的任务,并执行;如果是周期任务,再次放到DelayedWorkQueue

提交定时任务

ScheduledFuture<?> schedule(Runnable command,

long delay, TimeUnit unit)

非周期性任务,延时delay后执行,且没有返回参数

<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {

非周期性任务,延时delay后执行,有返回参数

ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

周期性任务,延时delay后,按照period周期执行,没有返回参数

ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit)

和上面的很类似,区别:

scheduleAtFixedRate:period指两次任务开始执行的间隔(如果任务执行时间大于period,则两次任务会同时执行)

scheduleWithFixedDelay:period指一次任务执行完后,下一次任务的间隔。

ForkJoinPool

ForkJoinPool是一种特殊的线程池,继承AbstractExecutorService,适用于计算型的并行执行任务,主要是利用其“任务分治”、“任务窃取”特性;

任务分治

利用fork/join机制,将大任务,拆分成多个小任务,最终再合并小任务的结果:

任务用ForkJoinTask表示(当然ForkJoinPool也可以提交Runnable/Callable),有两个了类:

RecursiveTask:有返回值

RecursiveAction:没有返回值

我们写自己的任务时,一般继承上面的2个类,然后重写其compute方法,在compute方法里完成的任务的拆分、合并

示例:

下面以一个没有返回值的大任务为例,介绍一下RecursiveAction的用法

大任务是:打印0-100的数值。

小任务是:每次只能打印20个数值。

public class RaskDemo extends RecursiveAction {
    /**
     *  每个"小任务"最多只打印20个数
      */
    private static final int MAX = 20;

    private int start;
    private int end;

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

    @Override
    protected void compute() {
        //当end-start的值小于MAX时,开始打印
        if((end-start) < MAX) {
            for(int i= start; i<end;i++) {
                System.out.println(Thread.currentThread().getName()+"i的值"+i);
            }
        }else {
            // 将大任务分解成两个小任务
            int middle = (start + end) / 2;
            RaskDemo left = new RaskDemo(start, middle);
            RaskDemo right = new RaskDemo(middle, end);
            left.fork();
            right.fork();
        }
    }
}

-------------------------------------------------------------------------------
public static void main(String[] args) throws Exception{
    // 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool
    ForkJoinPool forkJoinPool = new ForkJoinPool();

    // 提交可分解的PrintTask任务
    forkJoinPool.submit(new RaskDemo(0, 1000));

    //阻塞当前线程直到 ForkJoinPool 中所有的任务都执行结束
    forkJoinPool.awaitTermination(2, TimeUnit.SECONDS);

    // 关闭线程池
    forkJoinPool.shutdown();
}

运行结果:

从上面结果来看,ForkJoinPool启动了四个线程来执行这个打印任务,我的计算机的CPU是四核的。大家还可以看到程序虽然打印了0-999这一千个数字,但是并不是连续打印的,这是因为程序将这个打印任务进行了分解,分解后的任务会并行执行,所以不会按顺序打印

任务窃取

下面有3个工作线程worker1、worker2、worker3,每个线程都有自己的队列(用双端队列Dqueue实现,实现LIFO-Last In First Out)。

当worker3的队列为空时,则从worker1、worker2的队列的头部窃取任务来执行,充分利用CPU

构造函数

public ForkJoinPool(int parallelism,
                    ForkJoinWorkerThreadFactory factory,
                    UncaughtExceptionHandler handler,
                    boolean asyncMode) {

parallelism

并行度,相当于线程池中的线程数,默认Runtime.getRuntime().availableProcessors(),最大32767

ForkJoinWorkerThreadFactory

线程创建工厂

UncaughtExceptionHandler

asyncMode

是否异步模式,下面是源码:

asyncMode ? FIFO_QUEUE : LIFO_QUEUE,

FIFO即先进先出,即异步处理

LIFO即后进选出,即同步处理(有个问题:如果任务多,那先进的可能一直处理不了?)

附件

参考:

https://blog.csdn.net/ZHANGLIZENG/article/details/127833510

https://blog.csdn.net/weixin_42039228/article/details/123206215

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值