互联网技术12——Executor框架

Executor创建线程池

为了更好的控制多线程,JDK提供了一套线程框架Executor,帮助开发人员有效的控制线程。他们都在java.util.concurrent包中,是JDK并发包的核心。其中有一个比较重要的类;excutors,它扮演者线程工厂的角色,我们可以通过Executor可以创建特定功能的线程池。

Exexutor创建线程池的方法:

1. newFixedThreadPool() : 该方法广返回一个固定数量的线程池,该方法的线程池数量始终不变,当有一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂时存放在一个任务队列中,等待有空闲的线程取执行。

源码如图:

2.newSingleThreadPool():创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中

源码如图:

3.newCachedThreadPool():该方法返回一个可以根据实际情况调整线程个数的线程池,不限制最大线程数量,只要有任务来,就创建一个线程取执行任务。若无任务则不创建线程,并且每一个空闲线程会在60后自动回收

源码如图:

4.newScheduleThreadPool():该方法返回一个ScheduledExecutorService对象,但该线程池可以指定线程的数量,其中的每一个线程都可以实现定时器的作用。

源码如图:

 

底层调用:

无论创建那种类型的线程池,最低层还是调用了 下面的ThreadPoolExecutor,不同的线程池传递给ThreadPoolExector的参数不同。

源码如图:

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

1.第一个参数: 核心线程数,即线程池初始化时就创建的线程个数;

2.第二个参数:线程池中最大线程数

3.第三个参数:线程池中每一个线程保持活着的空闲时间。

4.第四个参数:时间单位

5.第五个参数:指定一个阻塞队列,若线程池中线程都忙着,再有任务过来,则排队等待

6.第六个参数:线程工厂

7.第七个参数:拒绝执行的handler,具体拒绝执行的逻辑

 

自定义线程池中对于队列是什么类型的比较关键:

1.有界队列:在使用有界队列时。若有新任务需要执行,如果线程池实际线程数小于核心线程数(corePoolSize),则有优先创建线程。若大于corePoolSize则会将任务加入到队列中,若队列已满,则在总线程数不大于最大线程数(maximumPoolSize)的前提下,创建新的线程,若线程数大于maximumPoolSize,则执行拒绝策略。如对列类型为:arrayBlockQueue。

2.无界队列:与有界队列相比,除非系统资源耗尽,否则无界队列的任务不存在任务入队失败的情况。当有新任务到来时,系统的线程数小于corePoolSize时,则新建线程执行任务,当达到corePoolSize后,就不会继续增加。若获取仍有新的任务加入,而又没有空闲的线程资源,则任务直接进入队列。若任务创建和处理速度差异很大,无界队列会保持快速增长,直到耗尽系统资源。

 

自定义线程池使用有界队列的例子:

待执行任务:

package com.company.threadPoolExecutorMySelf;

/**
 * Created by BaiTianShi on 2018/8/22.
 */
public class MyTask implements Runnable {

    private String id;
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public MyTask(String id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("正在处理id为:"+this.id+"的任务数据");
        try {
            //模拟处理过程
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public String toString(){
        return this.id;
    }
}

使用自定义线程池:

package com.company.threadPoolExecutorMySelf;

import com.company.masterWorker.Main;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Created by BaiTianShi on 2018/8/22.
 */
public class ThreadPoolExecutorMySelf {

    public static void main(String[] args) {
        ThreadPoolExecutor t = new ThreadPoolExecutor(
                1,
                2,
                60,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(3),
                new MyRejected()

        );

        MyTask m1 = new MyTask("1","任务1");
        MyTask m2 = new MyTask("2","任务2");
        MyTask m3 = new MyTask("3","任务3");
        MyTask m4 = new MyTask("4","任务4");
        MyTask m5 = new MyTask("5","任务5");
        MyTask m6 = new MyTask("6","任务6");
        t.execute(m1);
        t.execute(m2);
        t.execute(m3);
        t.execute(m4);
        t.execute(m5);
        t.execute(m6);
        // shutdown和shutdownNow的区别
        //1. shutdown 表示当前线程池状态变为SHUTDOWN,不再允许添加元素了,否则会抛出RejectExecutionException异常.
        //2.shutdownNow 表示线程池的状态立即变为STOP状态,并试图停止所有正在执行的线程,不再处理还在等待着的任务,并且会立即返回那些没执行的任务
        t.shutdown();
    }
}

自定义拒绝策略:

package com.company.threadPoolExecutorMySelf;

import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * Created by BaiTianShi on 2018/8/22.
 */
public class MyRejected implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println("线程被拒绝");
        System.out.println(r.toString()+"被拒绝加入队列,将以日志的形式输出");
    }
}

运行结果

线程被拒绝
正在处理id为:5的任务数据
正在处理id为:1的任务数据
6被拒绝加入队列,将以日志的形式输出
正在处理id为:2的任务数据
正在处理id为:3的任务数据
正在处理id为:4的任务数据

在上述代码中,通过自定义线程池来执行任务,根据ThreadPoolExecutor使用有界队列特性我们分析一下整个过程。

1.有6个任务准备加入队列,核心线程数为1,所以任务m1直接被执行,

2.除m1外其他5个准备放入队列中,但是有界队列的长度为3,所以只有m2、m3、m4可以入队。

3.接下来只能在maximumPoolSize范围内扩展当前线程数maximumPoolSize是2,所以m1的线程加上执行m5的线程达到最大线程数。

4.m6没有队列可入,也不能被马上执行m6只能被拒绝策略执行,实际应用中我们可以以日志的形式记录等

 

自定义线程池使用无界队列的例子:

package com.company.threadPoolExecutorMySelf;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by BaiTianShi on 2018/8/22.
 */
public class ThreadPoolExecutorMySelf2 implements Runnable {

    private static AtomicInteger count = new AtomicInteger(0);
    @Override
    public void run() {
        int tem = count.incrementAndGet();
        
        System.out.println("任务:"+tem);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue();
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                5,
                10,
                120,
                TimeUnit.SECONDS,
                queue
        );

       for(int i=0;i<20;i++){

               ThreadPoolExecutorMySelf2 t = new ThreadPoolExecutorMySelf2();
               pool.execute(t);

       }
        try {
            Thread.sleep(1000);
            System.out.println("队列长度:"+queue.size());
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

运行结果:


任务:1
任务:2
任务:3
任务:4
任务:5
队列长度:15
任务:6
任务:7
任务:8
任务:10
任务:9
任务:11
任务:15
任务:14
任务:13
任务:12
任务:16
任务:20
任务:19
任务:18
任务:17

可见,当达到corePoolSize时,任务会全部进入到无界队列中等待,直到有空闲线程来执行队列中的任务。

 

JDK拒绝策略:

JDK提供了四种拒绝策略,但是都不是很实用,这里简单介绍一下

AbortPolicy:直接抛出异常阻止系统正常工作

CallerRunPolicy:只要线程池未关闭,改策略直接在调用者线程池中,运行当前被丢弃的任务

DiscardOldPolicy:丢弃最老的一个请求,尝试再次提交当前任务

DiscardPoilicy:丢弃无法处理的任务,不给于任务处理

这里我们一DiscardOldPolicy为例做一个演示:

主线程:

package com.company.threadPoolExecutorMySelf;

import com.company.masterWorker.Main;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Created by BaiTianShi on 2018/8/22.
 */
public class ThreadPoolExecutorMySelf {

    public static void main(String[] args) {
        ThreadPoolExecutor t = new ThreadPoolExecutor(
                1,
                2,
                60,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(3),
               new ThreadPoolExecutor.DiscardOldestPolicy()

        );

        MyTask m1 = new MyTask("1","任务1");
        MyTask m2 = new MyTask("2","任务2");
        MyTask m3 = new MyTask("3","任务3");
        MyTask m4 = new MyTask("4","任务4");
        MyTask m5 = new MyTask("5","任务5");
        MyTask m6 = new MyTask("6","任务6");
        t.execute(m1);
        t.execute(m2);
        t.execute(m3);
        t.execute(m4);
        t.execute(m5);
        t.execute(m6);
        // shutdown和shutdownNow的区别
        //1. shutdown 表示当前线程池状态变为SHUTDOWN,不再允许添加元素了,否则会抛出RejectExecutionException异常.
        //2.shutdownNow 表示线程池的状态立即变为STOP状态,并试图停止所有正在执行的线程,不再处理还在等待着的任务,并且会立即返回那些没执行的任务
        t.shutdown();
    }
}

任务:

package com.company.threadPoolExecutorMySelf;

/**
 * Created by BaiTianShi on 2018/8/22.
 */
public class MyTask implements Runnable {

    private String id;
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public MyTask(String id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("正在处理id为:"+this.id+"的任务数据");
        try {
            //模拟处理过程
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public String toString(){
        return this.id;
    }
}

运行结果:

正在处理id为:5的任务数据
正在处理id为:1的任务数据
正在处理id为:3的任务数据
正在处理id为:4的任务数据
正在处理id为:6的任务数据

由于使用的有界队列ArrayBlockingQueue,且提交了6个任务,第6个任务到来时,线程池中已有2个线程正在处理任务,且队列已满,且线程池的最大线程数为2。因为拒绝策略使用的是DiscardOldestPolicy,对其oldest的一个任务2进行删除,并尝试提交当前的任务6,这样任务6就进入了队列中。任务2被丢弃。

自定义拒绝策略

 参见本篇关于ThreadPoolExecutor使用有界队列ArraLockerdQueue的例子,此里中实现并使用了自定义拒绝策略:MyRejected.java

 

 

 

 

 

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_28240551/article/details/81917146