重拾Java基础知识:线程池

前言

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术产生的原因。比如大家所熟悉的数据库连接池正是遵循这一思想而产生的。

为什么要用线程池?

《Java 并发编程的艺术》一书中说过,使用线程池的好处:

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统 的稳定性,使用线程池可以进行统一的分配,调优和监控。

Executor 框架

Executor 框架是 Java 5 之后引进的,Executor提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供Runnable对象,将任务的运行逻辑提交到执行器(Executor)中,由Executor框架完成线程的调配和任务的执行部分。在这里插入图片描述
其中Executor代表线程池的接口,有一个 execute() 方法。ExecutorService继承自Executor提供了线程池的一些生命周期方法。ThreadPoolExecutor维护着多个线程,等待着监督管理者分配可并发执行的任务。

创建线程池

ThreadPoolExecutor 类中提供的四个构造方法。我们以最长的也是用户自定义最全面的构造方法来进行讲解:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        //省略部分代码... ...
    }

先来介绍下它们每个参数的含义:

  1. corePoolSize:线程池的核心线程数量,新创建的线程池,默认线程数量为0。当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使有其他空闲线程可以处理任务也会创新线程,等到工作的线程数大于核心线程数时就不会在创建。
  2. maximumPoolSize:线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。可以调用setMaximumPoolSize()改变运行的最大线程的数目。如果我们使用了无界队列(比如:LinkedBlockingQueue,没有设定固定大小相当于 “无界”),直接入列,直到溢出。
  3. keepAliveTime:线程的闲置时间,当线程池里面的线程数量大于 corePoolSize 的时候,多出来的线程在等待的时间之后会被释放掉。如果任务很多,并且每个任务的执行时间比较短,避免线程重复创建和回收,可以通过这个时间,提高线程的利用率。
  4. unitkeepAliveTIme的时间单位,可以选择的单位有天、小时、分钟、毫秒、微妙、千分之一毫秒和纳秒。
  5. workQueue:阻塞队列,用于缓存待处理任务。如果当前核心线程数已满,就会被存放再队列中。
  6. threadFactory:线程池中创建线程的工厂,可以用来设定线程名等。
  7. handler:饱和策略,当达到线程最大数量或任务队列最大存储能力时,提交新任务时采取的处理策略。

假设:创建核心线程数量为5,最大线程数为10,线程闲置时间为10秒的线程池,无界队列,拒绝策略抛出异常,如下。

public class ExecutorTest {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                5,10,60,
                TimeUnit.SECONDS,new LinkedBlockingQueue<>(),new ThreadPoolExecutor.AbortPolicy());
    }
}

工作模型图:
在这里插入图片描述

执行线程池

创建好线程池后,需要让他运行起来。有两种方法:execute()方法和submit()方法

  • execute()方法属于Executor接口;submit()方法属于ExecutorService接口,ExecutorService继承于Executor
  • execute()方法只能提交Runnable类型的任务;submit()方法不管提交的是Runnable还是Callable任务都可以。
  • execute()方法没有返回,而submit()方法有返回值,返回值类型为Future,通过这个Future 对象可以判断任务是否执行成功。
  • execute()方法会直接抛出任务执行时的异常,可以用try/catch来捕获;submit()方法会吃掉异常,如果不对返回值Future调用get()方法

示例代码:

public class ExecutorTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                5,10,60,
                TimeUnit.SECONDS,new LinkedBlockingQueue<>(),new ThreadPoolExecutor.AbortPolicy());
        //execute()方法:
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                throw new RuntimeException();
            }
        });
        //submit()方法:
        Future<?> submit = threadPoolExecutor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                throw new RuntimeException();
            }
        });
        //如果不进行get()调用你看不到错误
        submit.get();
    }
}
BlockingQueue

BlockingQueue继承了Queue的接口,实现类有:ArrayBlockingQueueLinkedBlockingQueueLinkedBlockingDequeSynchronousQueuePriorityBlockingQueueDelayQueueLinkedTransferQueue
在这里插入图片描述

抽象方法

  • 入队
  1. add(E e):当队列满已满,抛出异常。
public class ExecutorTest {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue(1);
        arrayBlockingQueue.add("a");
        arrayBlockingQueue.add("b");
        /** Output:
         * Exception in thread "main" java.lang.IllegalStateException: Queue full
         * 	at java.util.AbstractQueue.add(AbstractQueue.java:98)
         * 	at com.study.test.ExecutorTest.main(ExecutorTest.java:9)
         */
    }
}
  1. offer(E e):当队列已满,丢弃其它多余元素。
public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue(1);
        arrayBlockingQueue.offer("a");
        arrayBlockingQueue.offer("b");
        System.out.println(arrayBlockingQueue);
        /** Output:
         *  [a]
         */
    }
}
  1. put(E e):当队列已满,一直阻塞。
public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue(1);
        arrayBlockingQueue.put("a");
        arrayBlockingQueue.put("b");
        System.out.println(arrayBlockingQueue);
    }
}
  • 出队
  1. poll():获取队头的元素,并将队列中的该元素删除,如果队列为空,直接返回null。
public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue(1);
        arrayBlockingQueue.put("a");
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        /** Output:
         *  a
         *  null
         */
    }
}
  1. take():获取队头的元素,并将队列中的该元素删除,阻塞方法,会一直等到有元素获取到才会返回。
public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue(1);
        arrayBlockingQueue.put("a");
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        /** Output:
         *  a
         *  
         */
    }
}

当然还有其它方法,比如:remove()方法删除元素,等。

ArrayBlockingQueue数组型阻塞队列。一个由数组支持的有界阻塞队列(需要为其指定容量)。此队列按 FIFO(先进先出)原则对元素进行排序。队列的头部 是在队列中存在时间最长的元素。队列的尾部 是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列检索操作则是从队列头部开始获得元素(案例如上)。

部分源码片段:

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    private static final long serialVersionUID = -817911632652898426L;

    final Object[] items;
    }

LinkedBlockingQueue链表结构的阻塞队列。底层基于单向连表实现,是一个单向队列,如果不指定容量,默认为Integer.MAX_VALUE,也就是无界队列。

部分源码片段:

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    private static final long serialVersionUID = -6903933977591709194L;

    static class Node<E> {
        E item;

        Node<E> next;

        Node(E x) { item = x; }
    }
    }

LinkedBlockingDeque链表结构的阻塞队列。底层基于单向链表实现,是一个双向队列,即可以从队列的两端插入和移除元素,相比于其他阻塞队列,LinkedBlockingDeque多了addFirst()addLast()peekFirst()peekLast()等方法。

代码示例:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        LinkedBlockingDeque<String> linkedBlockingDeque = new LinkedBlockingDeque(3);
        linkedBlockingDeque.add("a");
        linkedBlockingDeque.addLast("b");
        linkedBlockingDeque.addFirst("c");
        System.out.println(linkedBlockingDeque);
        System.out.println(linkedBlockingDeque.peekFirst());
        System.out.println(linkedBlockingDeque.peekLast());
        /** Output:
         *  [c, a, b]
         *  c
         *  b
         */
    }
}

SynchronousQueue一个不存储元素的阻塞队列。每个插入(生产者)操作必须等到另一个线程调用删除(消费者)操作,否则插入操作一直处于阻塞状态,而每一个删除操作也必须等待其他线程的插入操作。

代码示例:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                1,2,10,TimeUnit.SECONDS
                ,new SynchronousQueue(),new ThreadPoolExecutor.AbortPolicy());

        SynchronousQueue<String> synchronousQueue = new SynchronousQueue();
        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronousQueue.put("a");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("生产者完成");
            }
        });

        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronousQueue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者完成");
            }
        });

        /** Output:
         *  消费者完成
         *  生产者完成
         */
    }
}

PriorityBlockingQueue一个无界的阻塞队列,同时是一个支持优先级的队列(只能指定初始的队列大小,后面插入元素的时候,如果空间不够的话会自动扩容),内部使用堆算法保证每次出队都是优先级最高的元素。

DelayQueue一个无界阻塞队列。可以用做延时处理,队列里的元素并不是按照先进先出的规则,添加进DelayDeque的元素会经过compareTo()方法计算,然后按照时间进行排序,排在队头的元素是最早到期的,越往后到期时间越长,DelayDeque只能接受Delayed接口类型。

代码示例:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        DelayQueue delayQueue = new DelayQueue();
        delayQueue.offer(new DelayQueueTest());
    }
}
class DelayQueueTest implements Delayed{

    @Override
    public long getDelay(TimeUnit unit) {
        return System.currentTimeMillis();
    }

    @Override
    public int compareTo(Delayed o) {
        return (int) (getDelay(TimeUnit.SECONDS) - o.getDelay(TimeUnit.SECONDS));
    }
}

LinkedTransferQueue链表结构的无界阻塞队列。调用transfer()方法进入阻塞,如果没有其它线程进行消费,则插入失败,否则成功。

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                1, 2, 10, TimeUnit.SECONDS
                , new LinkedTransferQueue(), new ThreadPoolExecutor.AbortPolicy());

        LinkedTransferQueue<String> linkedTransferQueue = new LinkedTransferQueue();
        linkedTransferQueue.offer("a");
        linkedTransferQueue.offer("b");
        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    linkedTransferQueue.transfer("c");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("生产者完成");
            }
        });
        System.out.println(linkedTransferQueue);
        try {
            linkedTransferQueue.take();
            linkedTransferQueue.take();
            linkedTransferQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        /** Output:
         * [a, b]
         * 生产者完成
         */
    }
}

tryTransfer()方法并不会使得执行线程进入阻塞,如果没有消费者等待接收元素,则会立即返回false。

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                1, 2, 10, TimeUnit.SECONDS
                , new LinkedTransferQueue(), new ThreadPoolExecutor.AbortPolicy());

        LinkedTransferQueue<String> linkedTransferQueue = new LinkedTransferQueue();
        linkedTransferQueue.offer("a");
        linkedTransferQueue.offer("b");

        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                boolean c = linkedTransferQueue.tryTransfer("c");
                System.out.println(c);
                System.out.println("生产者完成");
            }
        });
        System.out.println(linkedTransferQueue);
        /** Output:
         * [a, b]
         * false
         * 生产者完成
         */
    }
}

SynchronousQueueLinkedBlockingQueue相比,LinkedTransferQueue性能更高(没有锁操作)。除了以上介绍的几种外,还有其它的一些队列比如DelayedWorkQueue等。

ThreadFactory

An object that creates new threads on demand. Using thread factories removes hardwiring of calls tonew Thread, enabling applications to use special thread subclasses, priorities, etc.
按需创建新线程的对象。 使用线程工厂消除了对新线程的硬连接调用,使应用程序能够使用特殊的线程子类、优先级等。

简单来说创建线程的工厂,可以设置线程的名字、优先级、线程数等等,用到了工厂模式的思想。在Executors类中有一个DefaultThreadFactory内部类,从源码可以看到这个线程工厂类为线程池进行的一些操作。

    /**
     * The default thread factory
     */
    static class DefaultThreadFactory 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;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

示例代码:

public class ThreadFactoryTest implements ThreadFactory {
    //原子计数器
    private final AtomicInteger count = new AtomicInteger();

    @Override
    public Thread newThread(Runnable r) {
        int i = count.incrementAndGet();
        Thread thread = new Thread(r);
        thread.setName("test_thread_name:" + i);
        System.out.println(thread.getName());
        return thread;
    }

    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                1, 2, 10
                , TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadFactoryTest());

        threadPoolExecutor.submit(() -> {
            System.out.println("start thread");
        });
        /** Output:
         *  test_thread_name:1
         *  start thread
         */
    }
}
RejectedExecutionHandler

当线程池中的任务缓存队列已满,且线程数目达到maximumPoolSize时,说明线程池处于饱和状态,如果还有任务到来就会采取一种拒绝策略。通常有四种策略:AbortPolicyCallerRunsPolicyDiscardOldestPolicyDiscardPolicy

  • AbortPolicy策略

默认拒绝策略,当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常。

下面创建一个线程池,核心线程为1,最大线程数为2,允许存放一个队列,代码如下所示:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,60
                ,TimeUnit.SECONDS,new ArrayBlockingQueue<>(1),new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 4; i++) {
            threadPoolExecutor.submit(new ThreadTest());
        }
        /** Output:
         *  pool-1-thread-1
         *  pool-1-thread-2
         *  pool-1-thread-2
         *  Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@511d50c0 rejected
         * from java.util.concurrent.ThreadPoolExecutor@60e53b93[Running, pool size = 2, active threads = 1, queued tasks = 0, completed tasks = 2]
         * 	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
         * 	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
         * 	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
         * 	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
         * 	at com.study.test.ExecutorTest.main(ExecutorTest.java:10)
         */
    }
}

public class ThreadTest implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
  • CallerRunsPolicy策略

当任务添加到线程池中被拒绝时,由创建了线程池的线程来执行被拒绝的任务。

代码如下所示:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,60
                ,TimeUnit.SECONDS,new ArrayBlockingQueue<>(1),new ThreadPoolExecutor.CallerRunsPolicy());
        for (int i = 0; i < 4; i++) {
            threadPoolExecutor.submit(new ThreadTest());
        }
        /** Output:
         *  main
         *  pool-1-thread-2
         *  pool-1-thread-1
         *  pool-1-thread-2
         */
    }
}

public class ThreadTest implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

可以看到由main()方法去执行了此任务。

  • DiscardOldestPolicy策略

丢弃任务队列最先加入的任务,然后将被拒绝的任务添加到等待队列中。

代码如下所示:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,60
                ,TimeUnit.SECONDS,new ArrayBlockingQueue<>(1),new ThreadPoolExecutor.DiscardOldestPolicy());
        for (int i = 0; i < 4; i++) {
            threadPoolExecutor.submit(new ThreadTest());
        }
        /** Output:
         *  pool-1-thread-1
         *  pool-1-thread-2
         *  pool-1-thread-1
         */
    }
}
public class ThreadTest implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

这样看似乎并不明显,线程1或线程2都有可能最先加入被丢弃。

  • DiscardPolicy策略

线程池直接丢弃拒绝的任务,不会抛异常。

代码如下所示:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,60
                ,TimeUnit.SECONDS,new ArrayBlockingQueue<>(1),new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 4; i++) {
            threadPoolExecutor.submit(new ThreadTest());
        }
        /**  Output:
         *  pool-1-thread-1
         *  pool-1-thread-2
         *  pool-1-thread-1
         */
    }
}
public class ThreadTest implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

可以看到打印结果中没有抛出异常,也没有执行,直接丢弃。

除了以上几种我们还可以自定义拒绝策略实现RejectedExecutionHandler接口的rejectedExecution方法就可以了,有兴趣的可以自己去研究。

关闭线程池

线程池提供了两个关闭方法,shutdownNow()方法和shuwdown()方法。

  • shutdownNow()方法:线程池拒接收新提交的任务,同时立马关闭线程池,将线程池的状态设置未STOP状态,线程池里的任务不再执行。

示例代码:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,60
                ,TimeUnit.SECONDS,new ArrayBlockingQueue<>(1),new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 4; i++) {
            threadPoolExecutor.submit(new ThreadTest());
        }
        threadPoolExecutor.shutdown();
        /** Output:
         *  pool-1-thread-2
         *  pool-1-thread-1
         */
    }
}
  • shutdown()方法:线程池拒接收新提交的任务,已经添加的任务会继续执行直到完成(包括已经加入到任务队列中的),同时将线程池的状态设置为SHUTDOWN状态。

示例代码:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,60
                ,TimeUnit.SECONDS,new ArrayBlockingQueue<>(1),new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 4; i++) {
            threadPoolExecutor.submit(new ThreadTest());
        }
        threadPoolExecutor.shutdown();
        /** Output:
         *  pool-1-thread-2
         *  pool-1-thread-1
         *  pool-1-thread-2
         */
    }
}

注意:现在在阻塞的情况下,不会关闭。条件允许的情况下线程池也并不是立马就关闭,调用isShutdown()方法就会返回true。要想等待线程池关闭,还需调用awaitTermination()方法,这时调用isTerminaed()方法会返回true

执行过程

  1. 当提交一个新任务到线程池时,首先判断核心线程 corePoolSize 是不是满了,如果没有满,则从核心线程池中取一个线程来执行任务,否则就进入下一步。

  2. 线程池判断任务队列 workQueue 是否了,如果没有满,则将新提交的任务放入在这个任务队列里,等待被执行。如果任务队列满了,则进入下一步。

  3. 判断线程池里的线程达到了最大线程数 maximumPoolSize,如果没有,则创建一个新的线程来执行任务。如果已经满了,则交给拒绝策略来处理这个任务。

在这里插入图片描述

生命周期

线程池一共定义了五种状态:

  • RUNNING:运行状态。能够接受新提交的任务,线程池在构造前(new操作)是初始状态,一旦构造完成线程池就进入了执行状态,并且能够处理队列中的任务。
  • SHUTDOWN:关闭状态。不再接受新提交的任务,可以继续执行队列中的任务。
  • STOP:停止状态。不再接受新的任务,也不处理队列中的任务,中断正在处理的任务。
  • TIDYING:整理状态。所有的任务都已经终止,workCount(有效线程数为0)。
  • TERMINATED:终止状态。线程池彻底终止,就变成TERMINATED状态。

在这里插入图片描述

  1. 调用shundown()方法线程池的状态由RUNNING 转变 SHUTDOWN

  2. 调用shutdowNow()方法线程池的状态由RUNNING 转变 STOP

  3. 当任务队列和线程池均为空的时候 线程池的状态由STOP/SHUTDOWN 转变 TIDYING

  4. terminated()方法被调用完成之后,线程池的状态由TIDYING 转变 TERMINATED状态。

线程池监控

对线程池进行监控,方便在出现问题时,可以根据线程池的使用状况快速定位问题,获取线程池任务状况。

  • getActiveCount() 方法:线程池中正在执行任务的线程数量
  • getCompletedTaskCount() 方法:线程池已完成的任务数量,该值小于等于 taskCount
  • getCorePoolSize() 方法:线程池的核心线程数量
  • getLargestPoolSize()方法:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过,也就是达到了 maximumPoolSize
  • getMaximumPoolSize()方法:线程池的最大线程数量
  • getPoolSize()方法:线程池当前的线程数量
  • getTaskCount() 方法:线程池需要执行的任务总数

示例代码:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,60
                ,TimeUnit.SECONDS,new ArrayBlockingQueue<>(1),new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 3; i++) {
            threadPoolExecutor.submit(new ThreadTest());
        }
        System.out.println("activeCount: "+threadPoolExecutor.getActiveCount());
        System.out.println("completedTaskCount: "+threadPoolExecutor.getCompletedTaskCount());
        System.out.println("corePoolSize: "+threadPoolExecutor.getCorePoolSize());
        System.out.println("largestPoolSize: "+threadPoolExecutor.getLargestPoolSize());
        System.out.println("maximumPoolSize: "+threadPoolExecutor.getMaximumPoolSize());
        System.out.println("poolSize: "+threadPoolExecutor.getPoolSize());
        System.out.println("taskCount: "+threadPoolExecutor.getTaskCount());


        threadPoolExecutor.shutdown();
        /** Output:
         *  activeCount: 0
         *  completedTaskCount: 3
         *  corePoolSize: 1
         *  largestPoolSize: 2
         *  maximumPoolSize: 2
         *  poolSize: 2
         *  taskCount: 3
         */
    }
}
public class ThreadTest implements Runnable {
    @Override
    public void run() {

    }
}

Executors

Executors框架可以更简单方便的去创建线程池,由原来的四种创建方式变为了六种,分别为:newCachedThreadPoolnewFixedThreadPoolnewScheduledThreadPoolnewSingleThreadExecutornewWorkStealingPoolnewSingleThreadScheduledExecutor

  • newCachedThreadPool:自动回收空闲的线程。

可以看到核心线程数量为 0, 表示不会永久保留任何的线程,最大线程的数量是 Integer.MAX_VALUE,也就是说这类线程池没有核心线程,可以无限制的创建线程,但是当有大量线程处于空闲状态的时候,超过 60s 就会被销毁,内部采用SynchronousQueue队列。适合执行大量且耗时少的线程任务。不过如果线程无限增长,会导致内存溢出。

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

示例代码:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            executorService.submit(new ThreadTest());
        }
        executorService.shutdown();
        /** Output: 
         * 当前线程 pool-1-thread-1
         * 当前线程 pool-1-thread-4
         * 当前线程 pool-1-thread-3
         * 当前线程 pool-1-thread-2
         * 当前线程 pool-1-thread-1
         * 当前线程 pool-1-thread-5
         * 当前线程 pool-1-thread-7
         * 当前线程 pool-1-thread-8
         * 当前线程 pool-1-thread-6
         * 当前线程 pool-1-thread-6
         */
    }
    public class ThreadTest implements Runnable {
        @Override
        public void run() {
            System.out.println("当前线程 "+ Thread.currentThread().getName());
        }
    }
}

你会发现即使会无限的创建线程,但还是会重复调用已完成的线程。

  • newFixedThreadPool:固定数量的线程池。

该方法会创建一个固定大小的线程池,corePoolSize等于maximumPoolSize说明只有核心线程而没有非核心线程,可控制线程最大并发数,超出的线程会在队列中等待。工作队列采用LinkedBlockingQueue,即无界阻塞队列若处理不过来会引发OOM。

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

示例代码:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            executorService.submit(new ThreadTest());
        }
        executorService.shutdown();
        /** Output:
         * 当前线程 pool-1-thread-1
         * 当前线程 pool-1-thread-3
         * 当前线程 pool-1-thread-1
         * 当前线程 pool-1-thread-2
         * 当前线程 pool-1-thread-2
         * 当前线程 pool-1-thread-2
         * 当前线程 pool-1-thread-2
         * 当前线程 pool-1-thread-2
         * 当前线程 pool-1-thread-2
         * 当前线程 pool-1-thread-3
         */
    }
    public class ThreadTest implements Runnable {
        @Override
        public void run() {
            System.out.println("当前线程 "+ Thread.currentThread().getName());
        }
    }
}
  • newScheduledThreadPool:定时及周期性的线程池。

核心线程数量是固定的,非核心线程的数量是Integer.MAX_VALUE主要应用于执行定时或周期性的任务,使用DelayedWorkQueue无界优先级阻塞队列。主要应用于执行定时或周期性的任务。

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

示例代码:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
        //延迟5秒执行
        scheduledExecutorService.schedule(() -> {
            System.out.println("延迟5秒执行: " + Thread.currentThread().getName());
        }, 5, TimeUnit.SECONDS);
        //延迟1秒,每3秒执行
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("延迟1秒,每隔3秒执行: " + Thread.currentThread().getName());
        }, 1, 3, TimeUnit.SECONDS);
        /** Output:
         * 延迟1秒,每隔3秒执行: pool-1-thread-1
         * 延迟1秒,每隔3秒执行: pool-1-thread-2
         * 延迟5秒执行: pool-1-thread-1
         * 延迟1秒,每隔3秒执行: pool-1-thread-3
         * 延迟1秒,每隔3秒执行: pool-1-thread-3
         * 延迟1秒,每隔3秒执行: pool-1-thread-1
         */
    }
}
  • newSingleThreadExecutor:单线程的线程池,里面就一个核心线程数。

该方法创建单线程的线程池,corePoolSize=maxinumPoolSize=1,没有非核心线程,因为只有一个核心线程
工作队列采用的是无界的LinkedBlockingQueue阻塞队列。任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果核心线程异常的话,则创建一个线程去顶替核心线程(但始终保持单线程)如果阻塞队列中任务太多,单线程处理不完则会引发OOM。

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

示例代码:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            executorService.submit(new ThreadTest());
        }
        executorService.shutdown();
        /** Output:
         * 当前线程 pool-1-thread-1
         * 当前线程 pool-1-thread-1
         * 当前线程 pool-1-thread-1
         * 当前线程 pool-1-thread-1
         * 当前线程 pool-1-thread-1
         * 当前线程 pool-1-thread-1
         * 当前线程 pool-1-thread-1
         * 当前线程 pool-1-thread-1
         * 当前线程 pool-1-thread-1
         * 当前线程 pool-1-thread-1
         */
    }
    public class ThreadTest implements Runnable {
        @Override
        public void run() {
            System.out.println("当前线程 "+ Thread.currentThread().getName());
        }
    }
}
  • newWorkStealingPool:Java8新增线程池,任务窃取的线程池。

使用ForkJoinPool的好处是,把1个任务拆分成多个“小任务”分发到多个线程上执行。执行完成后,再将结果合并。之前的线程池中,多个线程共有一个阻塞队列,而newWorkStealingPool 中每一个线程都有一个自己的队列。当线程发现自己的队列没有任务了,就会到别的线程的队列里获取任务执行。可以简单理解为”窃取“。一般是自己的本地队列采取LIFO(后进先出),窃取时采用FIFO(先进先出),一个从头开始执行,一个从尾部开始执行,由于偷取的动作十分快速,会大量降低这种冲突,也是一种优化方式。availableProcessors()方法是获取当前系统可以的CPU核心数。

    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

示例代码:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newWorkStealingPool(3);
        System.out.println(Runtime.getRuntime().availableProcessors());
        for (int i = 0; i < 5; i++) {
            executorService.submit(new ThreadTest());
        }
        executorService.shutdown();
        /** Output:
         * 8
         * 当前线程 ForkJoinPool-1-worker-2
         * 当前线程 ForkJoinPool-1-worker-1
         * 当前线程 ForkJoinPool-1-worker-2
         * 当前线程 ForkJoinPool-1-worker-3
         * 当前线程 ForkJoinPool-1-worker-1
         */
    }
    public class ThreadTest implements Runnable {
        @Override
        public void run() {
            System.out.println("当前线程 "+ Thread.currentThread().getName());
        }
    }
}
  • newSingleThreadScheduledExecutor:单线程的定时及周期性的线程池。

newScheduledThreadPool线程池不同的是corePoolSize只有一个。

    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

示例代码:

public class ExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.schedule(()->{
            System.out.println("延迟3秒后执行 "+Thread.currentThread().getName());
        },3,TimeUnit.SECONDS);
        scheduledExecutorService.scheduleAtFixedRate(()->{
            System.out.println("延迟1秒,每隔3秒执行 "+Thread.currentThread().getName());
        },1,3,TimeUnit.SECONDS);
        /** Output:
         * 延迟1秒,每隔3秒执行 pool-1-thread-1
         * 延迟3秒后执行 pool-1-thread-1
         * 延迟1秒,每隔3秒执行 pool-1-thread-1
         * 延迟1秒,每隔3秒执行 pool-1-thread-1
         * 延迟1秒,每隔3秒执行 pool-1-thread-1
         */
    }
    public class ThreadTest implements Runnable {
        @Override
        public void run() {
            System.out.println("当前线程 "+ Thread.currentThread().getName());
        }
    }
}

阿里巴巴Java编程规范

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

合理的创建线程池

线程池数量的确定一直是困扰着程序员的一个难题,大部分程序员在设定线程池大小的时候就是随心而定。要想合理的配置线程池的大小,首先得分析任务的特性,线程池的任务类型有CPU密集型、IO密集型、混合型

  • CPU密集型:任务会消耗大量的CPU资源,可以设置比CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用CPU的空闲时间。

线程数目 = CPU核数 + 1

  • IO密集型:系统大部分时间处理I/O交互,而线程在处理I/O的时间段内不会占用CPU来处理,这时就可以将CPU交出给其它线程使用。因此在I/O密集型任务的应用中,我们可以多配置一些线程。

线程数目 = 2 * CPU核数

  • 混合型:考虑拆分成CPU密集型和IO密集型。若CPU密集型和IO密集型的处理时间差不多,可以拆分,会比串行效率高。若CPU密集型和IO密集型的处理时间相差很大,没必要拆分。

如何判断是 CPU 密集任务还是 IO 密集任务?

CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。但凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值