并发编程:(七)线程池

一、自定义线程池

1、图解

在这里插入图片描述

2、自定义任务队列

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 自定义任务队列
 */
public class BlockingQueue<T> {
    //1、任务队列
    private Deque<T> queue = new ArrayDeque<>();
    //2、锁
    private ReentrantLock lock = new ReentrantLock();
    //3、生产者条件变量
    private Condition fullWaitSet = lock.newCondition();
    //4、消费者条件变量
    private Condition emptyWaitSet = lock.newCondition();
    //5、容量
    private int capcity;

    public BlockingQueue(int capcity) {
        this.capcity = capcity;
    }

    /**
     * 带超时的阻塞获取
     * @param timeout 等待时间
     * @param unit 时间工具类
     * @return
     */
    public T pull(long timeout, TimeUnit unit){
        //加锁
        lock.lock();
        try {
            //将 timeout 统一转为纳秒
            long nanos = unit.toNanos(timeout);
            while (queue.isEmpty()){
                try {
                    //返回剩余时间
                    if (nanos <= 0) {
                        //等待超时返回空
                        return null;
                    }
                    nanos = emptyWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //唤醒生产者
            fullWaitSet.signal();
            //获取队列元素
            return queue.removeFirst();
        } finally {
            //解锁
            lock.unlock();
        }
    }

    /**
     * 阻塞获取
     */
    public T take(){
        //加锁
        lock.lock();
        try {
            while (queue.isEmpty()){
                try {
                    emptyWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //唤醒生产者
            fullWaitSet.signal();
            //获取队列元素
            return queue.removeFirst();
        } finally {
            //解锁
            lock.unlock();
        }
    }

    /**
     * 阻塞添加,如果阻塞队列满了,就会死等
     * @param task
     */
    public void put(T task){
        //加锁
        lock.lock();
        try {
            while (queue.size() == capcity) {
                try {
                    System.out.println("等待加入任务队列..." + task);
                    fullWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("加入任务队列:" + task);
            //添加元素到队列
            queue.addLast(task);
            //唤醒消费者
            emptyWaitSet.signal();
        } finally {
            //解锁
            lock.unlock();
        }
    }

    public boolean push(T task,long timeout,TimeUnit unit){
        //加锁
        lock.lock();
        try {
            //将 timeout 统一转为纳秒
            long nanos = unit.toNanos(timeout);
            while (queue.size() == capcity) {
                try {
                    System.out.println("等待加入任务队列..." + task);
                    if (nanos <= 0) {
                        return false;
                    }
                    nanos = fullWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("加入任务队列:" + task);
            //添加元素到队列
            queue.addLast(task);
            //唤醒消费者
            emptyWaitSet.signal();
            return true;
        } finally {
            //解锁
            lock.unlock();
        }
    }

    /**
     * 获取大小
     * @return
     */
    public int size(){
        lock.lock();
        try {
            return queue.size();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 拒绝策略
     * @param rejectPolicy
     * @param task
     */
    public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
        lock.lock();
        try {
            //判断队列是否已满
            if (queue.size() == capcity) {
                rejectPolicy.reject(this,task);
            } else {
                //有空闲
                System.out.println("加入任务队列" + task);
                queue.addLast(task);
                emptyWaitSet.signal();
            }
        } finally {
            lock.unlock();
        }
    }
}

3、自定义线程池

import java.util.HashSet;
import java.util.concurrent.TimeUnit;

/**
 * 自定义线程池
 */
public class ThreadPool {
    //任务对列
    private BlockingQueue<Runnable> taskQueue;
    //线程集合
    private HashSet<Worker> workers = new HashSet<>();
    //核心线程数
    private int coreSize;
    //获取任务的超时时间
    private long timeout;
    //时间工具类
    private TimeUnit timeUnit;
    //拒绝策略
    private RejectPolicy<Runnable> rejectPolicy;

    public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapcity, RejectPolicy<Runnable> rejectPolicy) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.taskQueue = new BlockingQueue<>(queueCapcity);
        this.rejectPolicy = rejectPolicy;
    }
    //执行任务
    public void execute(Runnable task){
        //当任务数没有超过 coreSize 时,直接交给 worker 对象执行
        synchronized (workers){
            if (workers.size() < coreSize) {
                Worker worker = new Worker(task);
                System.out.println("新增 worker:" + worker + "\ttask:" + task);
                //加入线程集合
                workers.add(worker);
                //启动
                worker.start();
            } else {
                //任务数超出 coreSize 时,加入任务队列,有多种拒绝策略
                taskQueue.tryPut(rejectPolicy,task);
            }
        }
    }


    class Worker extends Thread{
        private Runnable task;

        public Worker(Runnable task) {
            this.task = task;
        }

        @Override
        public void run() {
            //执行任务
            //1、当task不为空,执行任务
            //2、当task执行完毕,再接着从任务队列中获取任务并执行
            //while (task != null || (task = taskQueue.take()) != null){
            while (task != null || (task = taskQueue.pull(timeout,timeUnit)) != null){
                try {
                    System.out.println("正在执行..." + task);
                    task.run();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    task = null;
                }
            }
            synchronized (workers) {
                System.out.println("worker 被移除:" + this);
                workers.remove(this);
            }
        }
    }
}

4、自定义拒绝策略接口

/**
 * 拒绝策略
 * @param <T>
 */
@FunctionalInterface
public interface RejectPolicy<T> {
    void reject(BlockingQueue<T> queue,T task);
}

5、测试

/**
 * 测试自定义线程池
 */
public class Test3 {
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool(1,1000, TimeUnit.MILLISECONDS,1, (queue,task)->{
             //当队列满了,可以执行的操作:
             //1、死等方式
//            queue.put(task);
             //2、带超时等待
//            queue.push(task,1500,TimeUnit.MILLISECONDS);
             //3、让调用者放弃任务执行
//            System.out.println("放弃" + task);
             //4、让调用者抛出异常
//            throw new RuntimeException("任务执行失败" + task);
             //5、让调用者自己执行任务
            task.run();
        });
        for (int i = 0; i < 3; i++) {
            int j = i;
            threadPool.execute(()->{
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(j);
            });
        }
    }
}

二、ThreadPoolExecutor

1、图解

在这里插入图片描述

2、线程池状态

ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量。

状态名高3位接收新任务处理阻塞队列任务说明
RUNNING111YY
SHUTDOWN000NY不会接收新任务,但会处理阻塞队列剩余任务
STOP001NN会中断正在执行的任务,并抛弃阻塞队列任务
TIDYING010--任务全执行完毕,活动线程为 0 即将进入终结
TERMINATED011--终结状态

从数字上比较,TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING

这些信息存储到一个原子变量 ctl 中,目的是将线程池状态与线程个数合二为一,这样就可以用一次 cas 原子操作进行赋值。

// c 为旧值,ctlOf 返回结果为新值
ctl.compareAndSet(c,stlOf(targetState,workerCountOf(c)));

// rs 为高 3 位代表线程池状态,wc为低 29 位代表线程个数,ctl 是合并它们
private static int ctlOf(int rs, int wc) { return rs | wc; }

3、构造方法

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
        null :
    AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

参数说明:

  • corePoolSize: 线程池核心线程数,它的数量决定了添加的任务是开辟新的线程去执行,还是放到 workQueue 任务队列中去
  • maximumPoolSize: 最大线程数目,这个参数会根据你使用的 workQueue 任务队列的类型,决定线程池会开辟的最大线程数量
  • keepAliveTime: 空闲线程存活时间,当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁,针对救急线程
  • unit: keepAliveTime的单位
  • workQueue: 任务队列,被添加到线程池中,但尚未被执行的任务
  • threadFactory: 线程工厂,用于创建线程,一般用默认即可
  • handler: 拒绝策略;当任务太多来不及处理时,如何拒绝任务

工作方式:

线程池 corePoolSize=2,maximumPoolSize=3
阻塞队列
核心线程1
核心线程2
救急线程1
任务1
任务2
任务5
size=2
任务3
任务4
  • 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
  • 当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入 workQueue 队列排队,直到有空闲的线程。
  • 如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程来救急。
  • 如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现
  • 当高峰过去后,超过 corePoolSize 的救急线程如果一段时间没有任务做,需要结束,目的节省资源,这个时间由 keepAliveTime 和 unit 来控制。

4、workQueue任务队列

任务队列被分为提交队列、有界任务队列、无界任务队列、优先任务队列

1、直接提交队列

设置为 SynchronousQueue 队列,SynchronousQueue 是一个特殊的 BlockingQueue,它没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。

public class Test1 {
    private static ExecutorService executorService;

    public static void main(String[] args) {
        //maximumPoolSize设置为2,拒绝策略为AbortPolicy,直接抛出异常
        executorService = new ThreadPoolExecutor(
                                1,
                                2,
                                1000,
                                TimeUnit.MILLISECONDS,
                                new SynchronousQueue<Runnable>(),
                                Executors.defaultThreadFactory(),
                                new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 3; i++) {
            executorService.execute(new ThreadTask());
        }
    }
}
class ThreadTask implements Runnable{
    public ThreadTask() {
    }

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

运行结果:

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.itan.test6.ThreadTask@6d6f6e28 rejected from java.util.concurrent.ThreadPoolExecutor@135fbaa4[Running, pool size = 2, active threads = 1, queued tasks = 0, completed tasks = 1]
pool-1-thread-2
pool-1-thread-1
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at com.itan.test6.Test1.main(Test1.java:17)

说明:

当任务队列为 SynchronousQueue,创建的线程数大于 maximumPoolSize 时,直接执行了拒绝策略抛出异常。

使用 SynchronousQueue 队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数小于 maximumPoolSize ,则尝试创建新的进程,如果达到 maximumPoolSize 设置的最大值,则根据设置的 handler 执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,这种情况下,你需要对你程序的并发量有个准确的评估,才能设置最合适的 maximumPoolSize 数量,否则很容易就会执行拒绝策略。

2、有界的任务队列

有界任务队列可以使用 ArrayBlockingQueue 实现

public class Test1 {
    private static ExecutorService executorService;

    public static void main(String[] args) {
        //maximumPoolSize设置为2,拒绝策略为AbortPolicy
        executorService = new ThreadPoolExecutor(
                                1,
                                2,
                                1000,
                                TimeUnit.MILLISECONDS,
                                new ArrayBlockingQueue<Runnable>(10),
                                Executors.defaultThreadFactory(),
                                new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 3; i++) {
            executorService.execute(new ThreadTask());
        }
    }
}

说明:

使用 ArrayBlockingQueue 有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize 时,则会将新的任务加入到等待队列中。若等待队列已满,即超过 ArrayBlockingQueue 初始化的容量,则继续创建线程,直到线程数量达到 maximumPoolSize 设置的最大线程数量,若大于 maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在 corePoolSize 以下,反之当任务队列已满时,则会以 maximumPoolSize 为最大线程数上限。

3、无界的任务队列

无界任务队列可以使用 LinkedBlockingQueue 实现

public class Test1 {
    private static ExecutorService executorService;

    public static void main(String[] args) {
        //maximumPoolSize设置为2,拒绝策略为AbortPolicy
        executorService = new ThreadPoolExecutor(
                                1,
                                2,
                                1000,
                                TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                Executors.defaultThreadFactory(),
                                new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 100; i++) {
            executorService.execute(new ThreadTask());
        }
    }
}

说明:

使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是 corePoolSize 设置的数量,也就是说在这种情况下 maximumPoolSize 这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到 corePoolSize 后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。

4、优先任务队列

优先任务队列可以使用 PriorityBlockingQueue 实现

public class Test2 {
    private static ExecutorService executorService;

    public static void main(String[] args) {
        //优先任务队列
        executorService = new ThreadPoolExecutor(
                        1,
                        2,
                        1000,
                        TimeUnit.MILLISECONDS,
                        new PriorityBlockingQueue<Runnable>(),
                        Executors.defaultThreadFactory(),
                        new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 20; i++) {
            executorService.execute(new ThreadTask(i));
        }
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class ThreadTask implements Runnable,Comparable<ThreadTask>{
    private int priority;

    /**
     * 当前对象和其他对象做比较,当前优先级大就返回-1,优先级小就返回1,值越小优先级越高
     * @param o
     * @return
     */
    @Override
    public int compareTo(ThreadTask o) {
        return this.priority > o.priority ? -1 : 1;
    }

    @Override
    public void run() {
        try {
            //让线程阻塞,使后续任务进入缓存队列
            Thread.sleep(1000L);
            System.out.println("priority:" + this.priority + ",ThreadName:" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

priority:0,ThreadName:pool-1-thread-1
priority:9,ThreadName:pool-1-thread-1
priority:8,ThreadName:pool-1-thread-1
priority:7,ThreadName:pool-1-thread-1
priority:6,ThreadName:pool-1-thread-1
priority:5,ThreadName:pool-1-thread-1
priority:4,ThreadName:pool-1-thread-1
priority:3,ThreadName:pool-1-thread-1
priority:2,ThreadName:pool-1-thread-1
priority:1,ThreadName:pool-1-thread-1

说明:

除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行,且线程池的线程数一直为 corePoolSize,也就是只有一个。

通过运行的代码我们可以看出 PriorityBlockingQueue 它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过 corePoolSize 的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue 队列可以自定义规则根据任务的优先级顺序先后执行。

5、拒绝策略

在这里插入图片描述

一般在创建线程池时,为了防止资源被耗尽,任务队列都会选择创建有界任务队列,但这种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时需要指定 ThreadPoolExecutorRejectedExecutionHandler 参数即合理的拒绝策略来处理线程池 “超载” 的情况。

ThreadPoolExecutor 自带的拒绝策略如下:

  • AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作
  • CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行
  • DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交
  • DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失

也可以自定义拒绝策略,扩展 RejectedExecutionHandler 接口

public class Test1 {
    private static ExecutorService executorService;

    public static void main(String[] args) {
        //maximumPoolSize设置为2,拒绝策略为AbortPolicy
        executorService = new ThreadPoolExecutor(
                1,
                2,
                1000,
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(5),
                Executors.defaultThreadFactory(),
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println(r.toString()+"执行了拒绝策略");

                }});
        for (int i = 0; i < 10; i++) {
            executorService.execute(new ThreadTask1());
        }
    }
}
class ThreadTask1 implements Runnable{
    public ThreadTask1() {
    }

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

运行结果:

com.itan.test6.ThreadTask1@7f31245a执行了拒绝策略
pool-1-thread-1
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-1
pool-1-thread-1
com.itan.test6.ThreadTask1@6d6f6e28执行了拒绝策略
pool-1-thread-2
pool-1-thread-1

6、固定大小线程池newFixedThreadPool

Executors 类中提供了众多工厂方法来创建各种用途的线程池

实现代码:

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

特点:

  • 核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
  • 阻塞队列是无界的,可以放任意数量的任务

适用场景:

适用于任务量已知,相对耗时的任务

public class Test1 {
    public static void main(String[] args) {
        //创建一个固定大小的线程池
        ExecutorService pool = Executors.newFixedThreadPool(2);
        pool.execute(()->{
            System.out.println("[" + Thread.currentThread().getName() + "] - 1");
        });
        pool.execute(()->{
            System.out.println("[" + Thread.currentThread().getName() + "] - 2");
        });
        pool.execute(()->{
            System.out.println("[" + Thread.currentThread().getName() + "] - 3");
        });
    }
}
public class Test1 {
    public static void main(String[] args) {
        //创建一个固定大小的线程池,并使用线程工厂给线程自定义名字
        ExecutorService pool = Executors.newFixedThreadPool(2, new ThreadFactory() {
            private AtomicInteger t = new AtomicInteger(1);
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r,"mypool" + t.getAndIncrement());
            }
        });
        pool.execute(()->{
            System.out.println("[" + Thread.currentThread().getName() + "] - 1");
        });
        pool.execute(()->{
            System.out.println("[" + Thread.currentThread().getName() + "] - 2");
        });
        pool.execute(()->{
            System.out.println("[" + Thread.currentThread().getName() + "] - 3");
        });
    }
}

运行结果:

[mypool1] - 1
[mypool2] - 2
[mypool1] - 3

7、带缓冲线程池newCachedThreadPool

实现代码:

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

特点:

  • 核心线程数是 0 ,最大线程数时 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着:
    1. 全部都是救急线程(60s后可以回收)
    2. 救急线程可以无限创建
  • 队列采用了 SynchronousQueue,实现特点是,它没有容量,没有线程来取是放不进去队列中的

SynchronousQueue实现:

public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        SynchronousQueue<Integer> integers = new SynchronousQueue<>();
        new Thread(()->{
            try {
                System.out.println("放入 " + 1);
                integers.put(1);
                System.out.println("结束 " + 1);

                System.out.println("放入 " + 2);
                integers.put(2);
                System.out.println("结束 " + 2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        sleep(1);

        new Thread(()->{
            try {
                System.out.println("获取" + 1);
                integers.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t2").start();

        sleep(1);

        new Thread(()->{
            try {
                System.out.println("获取 " + 2);
                integers.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t3").start();
    }
}

运行结果:

放入 1
获取1
结束 1
放入 2
获取 2
结束 2

说明:

整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1 分钟后释放线程。

适用场景:

适合任务数比较密集,但每个任务执行时间较短的情况。

8、单线程线程池newSingleThreadExecutor

实现代码:

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

使用场景:

希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。

区别:

  • 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会创建一个线程,保证线程池正常工作。
  • Executors.newSingleThreadExecutor() 线程个数始终为 1,不能修改
    1. FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法。
  • Executors.newFixedThreadPool(1) 初始时为 1,以后还可以修改
    1. 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改。

示例:

public class Test3 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newSingleThreadExecutor();
        pool.execute(()->{
            System.out.println(Thread.currentThread().getName() + " " + 1);
            int i = 1 / 0;
        });
        pool.execute(()->{
            System.out.println(Thread.currentThread().getName() + " " + 2);
        });
        pool.execute(()->{
            System.out.println(Thread.currentThread().getName() + " " + 3);
        });
    }
}

运行结果:

Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
	at com.itan.day.Test3.lambda$main$0(Test3.java:11)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
pool-1-thread-1 1
	at java.lang.Thread.run(Thread.java:748)
pool-1-thread-2 2
pool-1-thread-2 3

9、提交任务

方法详解:

// 执行任务
void execute(Runnable command);

// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);

// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? entends Callable<T>> tasks) throws InterruptedException;

// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? entends Callable<T>> tasks,long timeout,TimeUnit unit) throws InterruptedException;

// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其他任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException,ExecutionException;

// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout,TimeUnit unit) throws InterruptedException,ExecutionException;

1、带有返回值的任务:

public class Test4 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(1);
		// 使用lambda表达式简化
        Future<String> future = pool.submit(()-> {
            System.out.println(Thread.currentThread().getName() + " running...");
            Thread.sleep(1000);
            return "ok";
        });
        System.out.println(Thread.currentThread().getName() + " " + future.get());
    }
}
pool-1-thread-1 running...
main ok

2、invokeAll执行所有任务:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService pool = Executors.newFixedThreadPool(2);
    List<Future<Object>> futures = pool.invokeAll(Arrays.asList(
        () -> {
            System.out.println("begin...");
            Thread.sleep(1000);
            return "1";
        },
        () -> {
            System.out.println("begin...");
            Thread.sleep(500);
            return "2";
        },
        () -> {
            System.out.println("begin...");
            Thread.sleep(2000);
            return "3";
        }
    ));
    futures.forEach(f->{
        try {
            System.out.println(f.get());
        } catch (InterruptedException |ExecutionException e ) {
            e.printStackTrace();
        }
    });
}

3、invokeAny谁先执行完就返回:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService pool = Executors.newFixedThreadPool(1);
    String result = pool.invokeAny(Arrays.asList(
        () -> {
            System.out.println("begin1...");
            Thread.sleep(1000);
            System.out.println("end1...");
            return "1";
        },
        () -> {
            System.out.println("begin2...");
            Thread.sleep(500);
            System.out.println("end2...");
            return "2";
        },
        () -> {
            System.out.println("begin3...");
            Thread.sleep(2000);
            System.out.println("end3...");
            return "3";
        }
    ));
    System.out.println(result);
}

10、关闭线程池

1、shutdown方法

/*
线程池状态变为 SHUTDOWN
 - 不会接收新任务
 - 但已提交任务会执行完
 - 此方法不会阻塞调用线程的执行
*/
void shutdown();

源码:

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // 修改线程池状态
        advanceRunState(SHUTDOWN);
        // 仅会打断空闲线程
        interruptIdleWorkers();
        onShutdown(); // 扩展点 ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    // 尝试终结(没有运行的线程可以立刻终结,如果有运行的线程也不会等)
    tryTerminate();
}

示例:

public class Test5 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        Future<Integer> result1 = pool.submit(() -> {
            System.out.println(Thread.currentThread().getName() + " task 1 running...");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " task 1 finish...");
            return 1;
        });
        Future<Integer> result2 = pool.submit(() -> {
            System.out.println(Thread.currentThread().getName() + " task 2 running...");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " task 2 finish...");
            return 2;
        });
        Future<Integer> result3 = pool.submit(() -> {
            System.out.println(Thread.currentThread().getName() + " task 3 running...");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " task 3 finish...");
            return 3;
        });
        System.out.println(Thread.currentThread().getName() + " shutdown");
        pool.shutdown();
        System.out.println(Thread.currentThread().getName() + " 继续执行");
    }
}

运行结果:

pool-1-thread-1 task 1 running...
pool-1-thread-2 task 2 running...
main shutdown
main 继续执行
pool-1-thread-1 task 1 finish...
pool-1-thread-2 task 2 finish...
pool-1-thread-1 task 3 running...
pool-1-thread-1 task 3 finish...

2、shutdownNow

/*
线程池状态会变为 STOP
 - 不会接收新任务
 - 会将队列中的任务返回
 - 并用 interrupt 的方式中断正在执行的任务
*/
List<Runnable> shutdownNow();

源码:

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // 修改线程池状态
        advanceRunState(STOP);
        // 打断所有线程
        interruptWorkers();
        // 获取队列中剩余任务
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    // 尝试终结
    tryTerminate();
    return tasks;
}

示例:

public static void main(String[] args) {
    ExecutorService pool = Executors.newFixedThreadPool(2);

    Future<Integer> result1 = pool.submit(() -> {
        System.out.println(Thread.currentThread().getName() + " task 1 running...");
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName() + " task 1 finish...");
        return 1;
    });
    Future<Integer> result2 = pool.submit(() -> {
        System.out.println(Thread.currentThread().getName() + " task 2 running...");
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName() + " task 2 finish...");
        return 2;
    });
    Future<Integer> result3 = pool.submit(() -> {
        System.out.println(Thread.currentThread().getName() + " task 3 running...");
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName() + " task 3 finish...");
        return 3;
    });
    System.out.println(Thread.currentThread().getName() + " shutdown");
    List<Runnable> runnables = pool.shutdownNow();
    System.out.println(Thread.currentThread().getName() + runnables);
}

运行结果:

pool-1-thread-1 task 1 running...
pool-1-thread-2 task 2 running...
main shutdown
main[java.util.concurrent.FutureTask@3b9a45b3]

3、其他方法

// 不在 RUNNING 状态的线程池,此方法就返回 true
boolean isShutdown();

// 线程池状态是否是 TERMINATED
boolean isTerminated();

// 调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事情,可以利用此方法等待
boolean awaitTermination(loong timeout,TimeUnit unit) throws InterruptedException;

11、任务调度线程池ScheduledExecutorService

任务调度线程池功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用,但是由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。

1、延时执行,只执行一次

public static void main(String[] args) {
    //添加两个任务,希望它们都在 1s 后执行
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
    pool.schedule(()->{
        System.out.println(new Date() + "task1");
        int i = 1 / 0;
        //出现异常,但是还能执行后续任务,不会受到异常的影响
    },1000, TimeUnit.MILLISECONDS);

    pool.schedule(()->{
        System.out.println(new Date() + "task2");
    },1000, TimeUnit.MILLISECONDS);
}

2、定时执行

public static void main(String[] args) {
    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
    //添加两个任务,希望它们都在 1s 后执行
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
    System.out.println(sdf.format(new Date()) + " " +  Thread.currentThread().getName() + " - start");
    //初始延迟,延迟间隔,时间单位;循环执行
    pool.scheduleAtFixedRate(()->{
        try {
            Thread.sleep(1000);
            //程序等待一秒,间隔一秒,每次执行将间隔一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(sdf.format(new Date()) + " " + Thread.currentThread().getName() + " - running...");
    },1000,1000, TimeUnit.MILLISECONDS);

    pool.scheduleWithFixedDelay(()->{
        System.out.println(sdf.format(new Date()) + " " + Thread.currentThread().getName() + " - running...");
        try {
            Thread.sleep(1000);
            //程序会等待一秒,再加上间隔一秒,每次执行将间隔两秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    },1000,1000, TimeUnit.MILLISECONDS);
}

12、正确处理线程池异常

1、使用try…catch捕获异常

public static void main(String[] args) {
    //添加两个任务,希望它们都在 1s 后执行
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
    pool.schedule(()->{
        System.out.println(new Date() + "task1");
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
        //出现异常,但是还能执行后续任务,不会受到异常的影响
    },1000, TimeUnit.MILLISECONDS);

    pool.schedule(()->{
        System.out.println(new Date() + "task2");
    },1000, TimeUnit.MILLISECONDS);
}

2、使用callable配合future处理

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService pool = Executors.newFixedThreadPool(1);
    Future<Boolean> future = pool.submit(() -> {
        System.out.println("task1");
        int i = 1 / 0;
        //出现异常
        return true;
    });
    //会将异常信息输出
    System.out.println(future.get());
}

13、定时任务应用

//每周四执行定时任务
public static void main(String[] args) {
    //获取当前时间
    LocalDateTime now = LocalDateTime.now();
    //获取周四时间6点
    LocalDateTime time = now.withHour(18).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
    //如果当前时间 > 本周周四,必须找到下周周四
    if (now.compareTo(time) > 0){
        time = time.plusWeeks(1);
    }
    //计算当前时间和周四时间的时间差
    long initailDelay = Duration.between(now, time).toMillis();
    //计算一周的间隔时间
    long period = 1000 * 60 * 60 * 24 * 7;
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
    pool.scheduleAtFixedRate(()->{
        System.out.println("running");
    },initailDelay,period,TimeUnit.MILLISECONDS);
}

三、异步模式之工作线程

1、定义

让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务。也可以将其归类为分工模式,它的典型实现就是线程池,也体现了经典设计模式中的享元模式。

注意:不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率

2、饥饿

固定大小线程池会有饥饿现象。

例如:

  • 两个工人是同一个线程池中的两个线程
  • 他们要做的事情是:为客人点餐和到后厨做菜,这是两个阶段的工作
    1. 客人点餐:必须先点完餐,等菜好了,上菜,在此期间处理点餐的工人必须等待
    2. 后厨做菜
  • 比如工人A处理了点餐任务,接下来他要等着工人B把菜做好,然后上菜,配合蛮好
  • 但现在同时来了两个客人,这个时候工人A和工人B都去处理点餐了,这时就没有人做饭了,出现饥饿

饥饿现象:

public class Test6 {
    static final List<String> MENU = Arrays.asList("麻婆豆腐","地三鲜","可乐鸡翅","炸鸡");
    static Random RANDOM = new Random();
    static String cooking(){
        return MENU.get(RANDOM.nextInt(MENU.size()));
    }
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);
        pool.execute(()->{
            System.out.println(Thread.currentThread().getName() + " - 处理点餐...");
            Future<String> future = pool.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " - 做菜...");
                return cooking();
            });
            try {
                System.out.println(Thread.currentThread().getName() + " - 上菜:" + future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        pool.execute(()->{
            System.out.println(Thread.currentThread().getName() + " - 处理点餐...");
            Future<String> future = pool.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " - 做菜...");
                return cooking();
            });
            try {
                System.out.println(Thread.currentThread().getName() + " - 上菜 " + future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}

运行结果:

pool-1-thread-1 - 处理点餐...
pool-1-thread-2 - 处理点餐...
// 都在等待做菜的线程

解决办法:

  1. 增加线程池的大小,但是不是最好的解决办法,若同一时间来了更多任务,还是会出现饥饿现象。
  2. 对不同的任务类型,采用不同的线程池,互不干扰。
public class Test6 {
    static final List<String> MENU = Arrays.asList("麻婆豆腐","地三鲜","可乐鸡翅","炸鸡");
    static Random RANDOM = new Random();
    static String cooking(){
        return MENU.get(RANDOM.nextInt(MENU.size()));
    }
    public static void main(String[] args) {
        //点餐线程池
        ExecutorService waiterPool = Executors.newFixedThreadPool(1);
        //做菜线程池
        ExecutorService cookPool = Executors.newFixedThreadPool(1);
        waiterPool.execute(()->{
            System.out.println(Thread.currentThread().getName() + " - 处理点餐...");
            Future<String> future = cookPool.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " - 做菜...");
                return cooking();
            });
            try {
                System.out.println(Thread.currentThread().getName() + " - 上菜:" + future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        waiterPool.execute(()->{
            System.out.println(Thread.currentThread().getName() + " - 处理点餐...");
            Future<String> future = cookPool.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " - 做菜...");
                return cooking();
            });
            try {
                System.out.println(Thread.currentThread().getName() + " - 上菜 " + future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}

运行结果:

pool-1-thread-1 - 处理点餐...
pool-2-thread-1 - 做菜...
pool-1-thread-1 - 上菜:麻婆豆腐
pool-1-thread-1 - 处理点餐...
pool-2-thread-1 - 做菜...
pool-1-thread-1 - 上菜 地三鲜

3、创建多少线程池合适

  1. 线程池数量过少会导致程序不能充分地利用系统资源,容易导致饥饿发生。
  2. 线程池数量过多会导致更多的线程上下文切换,占用更多内存。

1、CPU密集型运算

通常采用 CPU核数+1 能够实现最优的 CPU 利用率,+1 是保证当线程由于缺失故障(操作系统)或其他原因导致暂停时,额外的这个线程就能顶上去,保证 CPU 时钟周期不被浪费。

2、I/O密集型运算

CPU 不总是处于繁忙状态,例如,当你执行业务计算时,这时候会使用 CPU 资源,但当你执行 I/O 操作时,远程 RPC 调用时,包括进行数据库操作时,这时候 CPU 就空闲了,可以利用多线程提高它的利用率。

线程数 = 核数 * 期望 CPU 利用率 * 总时间(CPU计算时间 + 等待时间)/ CPU 计算时间

例如 4核CPU计算时间是50%,其他等待时间是50%,期望 CPU 被100% 利用,计算线程池数为

4 * 100% * 100% /50% = 8

四、Fork/Join

1、概述

Fork/Join 是JDK 1.7 加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的 CPU 密集型运算。

任务拆分就是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的一些计算,如归并排序、斐波那契数列,都可以用分治思想进行求解。

Fork/Join 在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运算效率。

Fork/Join 默认会创建与 CPU 核心数大小相同的线程池。

2、使用

提交给 Fork/Join 线程池的任务需要继承 RecursiveTask(有返回值)或 RecursiveAction(没有返回值)

1、求和任务:

class MyTask extends RecursiveTask<Integer>{
    private Integer n;

    public MyTask(Integer n) {
        this.n = n;
    }

    @Override
    public String toString() {
        return "{" + n + '}';
    }

    @Override
    protected Integer compute() {
        // 如果n已经为1,可以求得结果了
        if (n == 1) {
            System.out.println(Thread.currentThread().getName() + " join() " + n);
            return n;
        }
        // 将任务进行拆分(fork)
        MyTask task = new MyTask(n - 1);
        task.fork();
        System.out.println(Thread.currentThread().getName() + " fork() " + n + " + " + task);

        //合并结果(join)
        int result = n + task.join();
        System.out.println(Thread.currentThread().getName() + " join() " + n + " + " + task + " = " + result);
        return result;
    }
}

2、将任务提交给FarkJoinPool线程池来执行:

public class Test {
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool(4);
        System.out.println(pool.invoke(new MyTask(5)));
    }
}

3、运行结果:

ForkJoinPool-1-worker-1 fork() 5 + {4}
ForkJoinPool-1-worker-2 fork() 4 + {3}
ForkJoinPool-1-worker-2 fork() 2 + {1}
ForkJoinPool-1-worker-1 fork() 3 + {2}
ForkJoinPool-1-worker-2 join() 1
ForkJoinPool-1-worker-2 join() 2 + {1} = 3
ForkJoinPool-1-worker-1 join() 3 + {2} = 6
ForkJoinPool-1-worker-2 join() 4 + {3} = 10
ForkJoinPool-1-worker-1 join() 5 + {4} = 15
15
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值