第八章 并发工具之 自定义线程池

JUC并发编程系列文章

http://t.csdn.cn/UgzQi



前言


一、线程池

1、自定义线程池

在这里插入图片描述

代码示例


import lombok.extern.slf4j.Slf4j;

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

@Slf4j(topic = "c.testThread27")
public class testThread27 {
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool(1,1000L,TimeUnit.MILLISECONDS,1,
                (queue, runnable) -> {

                    /**
                     * 1、队列满了就死等
                     * queue.addT(runnable);
                     * 2、满了带超时等待,等待超时不再执行任务
                     * queue.addTime(runnable,500L,TimeUnit.MILLISECONDS);
                     * 3、让调用者放弃任务的执行,满了什么都不做,自然就放弃了
                     * log.debug("队列满了,放弃任务"+runnable);
                     * 4、让调用者抛出异常
                     * throw new RuntimeException("队列已经满了,不能执行任务了");
                     * 5、让调用者自己创建线程执行任务,不再从线程池获取
                     * runnable.run();
                     */
                    runnable.run();
                });
        for (int i =1; i<=5; i++){
            int j = i;
            threadPool.execute(()->{
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                log.debug("{}",j);
            });
        }

    }
}

//策略模式
@FunctionalInterface
interface RejectPolicy<T>{
    void reject(BlockingQueue<T> queue,Runnable runnable);
}



@Slf4j(topic = "c.ThreadPool")
//线程池
class ThreadPool {
    //任务队列
    private BlockingQueue<Runnable> taskQueue;

    //线程集合
    private HashSet<Work> workers = new HashSet();

    //线程核心数
    private int coreSize;

    //获取任务超时时间
    private Long tomeOut;

    private TimeUnit unit;
    
    //拒绝策略
    private RejectPolicy<Runnable> rejectPolicy;

    //初始化线程池
    public ThreadPool(int coreSize, Long tomeOut, TimeUnit unit,int queueCapacity,RejectPolicy rejectPolicy) {
        this.coreSize = coreSize;
        this.tomeOut = tomeOut;
        this.unit = unit;
        this.taskQueue = new BlockingQueue<>(queueCapacity);
        this.rejectPolicy = rejectPolicy;
    }

    //执行任务
    public void execute(Runnable runnable){
        //如果任务数coreSize 没有超过线程集合的长度 就交给处理任务,超过了放入队列
        synchronized (workers){
            if(workers.size() < coreSize){
                Work work = new Work(runnable);
                log.debug("新增工作线程="+work+"新增任务"+runnable);
                //把工作线程放入集合
                workers.add(work);
                work.start();
            }else {
                log.debug("任务数超过线程集合,放入任务队列"+runnable);
//                taskQueue.addT(runnable);
                /**
                 * 添加策略模式,下方权限给不同的任务自己去实现不同的业务,具体的操作抽象成一个接口,
                 * 具体的实现由调用者传递过来,具体的实现不写在 ThreadPool 线程池里面
                 * 1、队列满了就死等
                 * 2、满了带超时等待
                 * 3、让调用者放弃任务的执行
                 * 4、让调用者抛出异常
                 * 5、让调用者自己创建线程执行任务,不再从线程池获取
                 */
                taskQueue.tryPut(rejectPolicy,runnable);
            }
        }
    }



    //工作线程
    class Work extends Thread{
        private Runnable runnable;

        public Work(Runnable runnable) {
            this.runnable = runnable;
        }

        @Override
        public void run() {
            //当 runnable不为空就执行,为空去任务队列拿任务执行( 再赋值给 runnable 就不为空了,继续循环执行任务,不设置超时时间两个线程执行完所有任务还是会一直等待)
//            while (runnable != null || (runnable = taskQueue.take()) != null) {
            while (runnable != null || (runnable = taskQueue.pool(1000L,TimeUnit.MILLISECONDS)) != null) {
                                                   //有超时时间的会返回一个 null,条件不成立就会走//任务执行完后移除线程
                                                                              //            synchronized (workers){
                try {
                    log.debug("执行任务"+runnable);
                    runnable.run();
                    //执行完让 runnable 等于 null
                    runnable = null;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            //任务执行完后移除线程
            synchronized (workers){
                log.debug("任务执行完了,删除线程"+this);
                workers.remove(this);
            }
        }
    }
}

//阻塞队列
@Slf4j(topic = "c.BlockingQueue")
class BlockingQueue<T> {
    //线程池的任务队列
    private Deque<Runnable> queue = new ArrayDeque();
    //锁,队列的头部和尾部需要添加同步锁
    private ReentrantLock lock = new ReentrantLock();

    //队列满的条件变量
    private Condition fullCond = lock.newCondition();

    //队列空的条件变量
    private Condition emptyConf = lock.newCondition();

    //队列容量
    private int capacity;

    public BlockingQueue() {
    }

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

    //带超时的阻塞获取,无需一直等待
    public Runnable pool(Long timeOut, TimeUnit unit){
        lock.lock();
        try {
            long nanos = unit.toNanos(timeOut);
            //判断队列是否为空,为空就等待
            while (queue.isEmpty()){
                //如果第二次循环过来发现 nanos 等于 0 就是等待超时,没有等到被唤醒。
                if(nanos <= 0){
                    return null;
                }
                try {
                    //emptyConf.awaitNanos( 返回上一次等待的剩余时长,比如本来需要等待 2秒,在 1秒时被唤醒,
                    // 这时还要再走一遍循环,而这时就不需要再等 2 秒了,只需要等 1秒就够了。
                    nanos = emptyConf.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Runnable t = queue.removeFirst();
            fullCond.signal();
            return t;

        }finally {
            lock.unlock();
        }

    }



    //阻塞获取
    public Runnable take(){
        lock.lock();
        try {
            //判断队列是否为空,为空就等待
            while (queue.isEmpty()){
                try {
                    emptyConf.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Runnable t = queue.removeFirst();
            fullCond.signal();
            return t;

        }finally {
            lock.unlock();
        }

    }

    //阻塞添加
    public void addT(Runnable t){
        lock.lock();
        try {
            //判断队列是否已经满了
            while (queue.size() == capacity){
                try {
                    log.debug("队列已经满了,等待加入任务队列.....");
                    fullCond.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            queue.addLast(t);
            emptyConf.signal();
        }finally {
            lock.unlock();
        }

    }

    //带超时时间的阻塞添加
    public boolean addTime(Runnable t,Long timeOut,TimeUnit unit){
        lock.lock();
        try {
            //转换同一时间单位
            long nanos = unit.toNanos(timeOut);
            //判断队列是否已经满了
            while (queue.size() == capacity){
                try {

                    if(nanos <= 0){
                        //超时添加失败
                        return false;
                    }

                    nanos = fullCond.awaitNanos(nanos);
                    log.debug("队列已经满了,等待加入任务队列.....");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            queue.addLast(t);
            emptyConf.signal();
            //添加成功
            return true;
        }finally {
            lock.unlock();
        }

    }



    //队列容量
    public int getSize(){
        lock.lock();
        try {
            return queue.size();
        }finally {
            lock.unlock();
        }
    }


    //拒绝策略
    public void tryPut(RejectPolicy<T> rejectPolicy, Runnable runnable) {
        lock.lock();
        try {
            //判断队列是否已满,满了执行拒绝策略
            if(queue.size() == capacity){
                rejectPolicy.reject(this,runnable);
            }else {
                //没有满就加入队列
                log.debug("加入任务队列"+runnable);
                queue.addLast(runnable);
                emptyConf.signal();
            }
        }finally {
            lock.unlock();
        }

    }
}


2、ThreadPoolExecutor

在这里插入图片描述

1、线程池的状态

在这里插入图片描述

因为第一位是符号位,RUNNING 是负数,所以最小.

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

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

2、构造方法


public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                         RejectedExecutionHandler handler)

在这里插入图片描述

线程池工作方式

在这里插入图片描述
在这里插入图片描述

● 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务,线程池中的线程都是懒惰创建了,只有用到了才会创建。
● 当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排队,直到有空闲的线程。
● 如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程来救急。
● 如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现,其它著名框架也提供了实现
○ AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
○ CallerRunsPolicy 让调用者运行任务
○ DiscardPolicy 放弃本次任务
○ DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
○ Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方便定位问题
○ Netty 的实现,是创建一个新线程来执行任务
○ ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
○ PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
● 当高峰过去后,超过corePoolSize 的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由keepAliveTime 和 unit 来控制。

在这里插入图片描述

根据这个构造方法,JDK Executors类中提供了众多工厂方法来创建各种用途的线程池.

3、newFixedThreadPool 创建固定大小的线程池

在这里插入图片描述
在这里插入图片描述

4、newCachedThreadPool 带缓冲的线程池

在这里插入图片描述

SynchronousQueue<Integer> integers = new SynchronousQueue<>();

new Thread(() -> {
    try {
        log.debug("putting {} ", 1);
        integers.put(1);
        log.debug("{} putted...", 1);
        log.debug("putting...{} ", 2);
        integers.put(2);
        log.debug("{} putted...", 2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
},"t1").start();

sleep(1);

new Thread(() -> {
    try {
        log.debug("taking {}", 1);
        integers.take();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
},"t2").start();

sleep(1);

new Thread(() -> {
    try {
        log.debug("taking {}", 2);
        integers.take();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
},"t3").start();


输出


11:48:15.500 c.TestSynchronousQueue [t1] - putting 1 
11:48:16.500 c.TestSynchronousQueue [t2] - taking 1 
11:48:16.500 c.TestSynchronousQueue [t1] - 1 putted... 
11:48:16.500 c.TestSynchronousQueue [t1] - putting...2 
11:48:17.502 c.TestSynchronousQueue [t3] - taking 2 
11:48:17.503 c.TestSynchronousQueue [t1] - 2 putted...

评价 整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线程。
适合任务数比较密集,但每个任务执行时间较短的情况

5、newSingleThreadExecutor 单线程的线程池

在这里插入图片描述

6、提交任务

在这里插入图片描述

7、关闭线程池

在这里插入图片描述

// 尝试终结(没有运行的线程可以立刻终结,如果还有运行的线程也不会等)
tryTerminate();
如果线程池关闭以后还有运行的线程,线程执行完任务后会自动结束,会将已经添加到任务队列的任务执行完。shutdown执行后,线程和任务还是会执行,但是调用shutdown的线程并不会阻塞,而是直接执行后面的方法,这时如果想等待线程池中的线程将任务执行完,再往下继续运行,可以在调用shutdown的线程里面再次调用 awaitTimenation()方法等待池和任务队列的结束。

在这里插入图片描述
在这里插入图片描述

8、任务调度线程池

在这里插入图片描述

代码示例

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.*;

@Slf4j(topic = "c.TestStarvation")
public class TestStarvation {

    public static void main(String[] args) {
        //使用 ScheduledExecutorService 改写:线程池只有一个线程还是会串行执行,如果发生异常不会影响后面的线程任务,但发生异常的任务会处理失败
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

        log.debug("start...");

        //延时从上一个任务的结束时间开始算,再延时 delay 1 秒  的参数,延时3秒
        executor.scheduleWithFixedDelay(()-> {
            log.debug("running...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1, 1, TimeUnit.SECONDS);

    }

    //执行时间较长,就会影响到定时时间
    public void scheduleAtFixedRate(ScheduledExecutorService executor){
        log.debug("start...");
        //定时反复执行任务
        executor.scheduleAtFixedRate(() -> {
            log.debug("running...");
            //如果任务的执行时间较长,就会影响到定时时间
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1, 1, TimeUnit.SECONDS);
    }



    //使用 ScheduledExecutorService代替 timer 执行线程池任务调度
    public void executor(ScheduledExecutorService executor){
        //使用 ScheduledExecutorService 改写:线程池只有一个线程还是和timer一样会串行执行,
        // 如果发生异常不会影响后面的线程任务,但发生异常的任务会处理失败

        // 添加两个任务,希望它们都在 1s 后执行
        executor.schedule(() -> {
            System.out.println("任务1,执行时间:" + new Date());
            int i = 1/0;
            System.out.println("你好");
            try { Thread.sleep(2000); } catch (InterruptedException e) { }
        }, 1000, TimeUnit.MILLISECONDS);

        executor.schedule(() -> {
            System.out.println("任务2,执行时间:" + new Date());
        }, 1000, TimeUnit.MILLISECONDS);
    }


    //使用 timer 实现线程池任务调度
    public void timer(){
        Timer timer = new Timer();

        TimerTask task1 = new TimerTask() {
            @SneakyThrows
            @Override
            public void run() {
                log.debug("task 1");
                //发声异常任务,后面的任务就不会被调度了。直接废了
                int i = 1/0;
                Thread.sleep(2000);
            }
        };

        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                log.debug("task 2");
            }
        };

        log.debug("start...");
        // 使用 timer 添加两个任务,希望它们都在 1s 后执行
        // 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的延时,影响了『任务2』的执行
        // 甚至如果task1出异常停止后,task2都不会执行
        timer.schedule(task1, 1000);
        timer.schedule(task2, 1000);
    }
}

8.2、定时任务

代码示例:每星期四18点执行定时任务

import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class testThread28 {
    public static void main(String[] args) {
        // 获得当前时间
        LocalDateTime now = LocalDateTime.now();
        // 获取本周四 18:00:00.000
        LocalDateTime thursday =
                now.withHour(18).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
        // 如果当前时间已经超过 本周四 18:00:00.000, 那么找下周四 18:00:00.000
        if(now.compareTo(thursday) >= 0) {
            thursday = thursday.plusWeeks(1);
        }

         // 计算时间差,即延时执行时间
        long initialDelay = Duration.between(now, thursday).toMillis();//当前时间到下一周四18点的毫秒值
        // 计算间隔时间,即 1 周的毫秒值
        long oneWeek = 7 * 24 * 3600 * 1000;

        ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
        System.out.println("开始时间:" + new Date());

        executor.scheduleAtFixedRate(() -> {
            System.out.println("执行时间:" + new Date());
        }, initialDelay, oneWeek, TimeUnit.MILLISECONDS);
    }
}

9、正确处理任务异常

方法1:主动捉异常

ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(() -> {
    try {
        log.debug("task1");
        int i = 1 / 0;
    } catch (Exception e) {
        log.error("error:", e);
    }
});

输出
21:59:04.558 c.TestTimer [pool-1-thread-1] - task1 
21:59:04.562 c.TestTimer [pool-1-thread-1] - error: 
java.lang.ArithmeticException: / by zero 
 at cn.itcast.n8.TestTimer.lambda$main$0(TestTimer.java:28) 
 at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) 
 at java.util.concurrent.FutureTask.run(FutureTask.java:266) 
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) 
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) 
 at java.lang.Thread.run(Thread.java:748)

方法2:使用 Future

ExecutorService pool = Executors.newFixedThreadPool(1);

Future<Boolean> f = pool.submit(() -> {
    log.debug("task1");
    int i = 1 / 0;
    return true;
});
log.debug("result:{}", f.get());

输出
21:54:58.208 c.TestTimer [pool-1-thread-1] - task1 
Exception in thread "main" java.util.concurrent.ExecutionException: 
java.lang.ArithmeticException: / by zero 
 at java.util.concurrent.FutureTask.report(FutureTask.java:122) 
 at java.util.concurrent.FutureTask.get(FutureTask.java:192) 
 at cn.itcast.n8.TestTimer.main(TestTimer.java:31) 
Caused by: java.lang.ArithmeticException: / by zero 
 at cn.itcast.n8.TestTimer.lambda$main$0(TestTimer.java:28) 
 at java.util.concurrent.FutureTask.run(FutureTask.java:266) 
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) 
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) 
 at java.lang.Thread.run(Thread.java:748)

在这里插入图片描述

10、TomCat线程池

在这里插入图片描述

在这里插入图片描述

扩展了 ThreadPoolExecutor

Tomcat 线程池扩展了 ThreadPoolExecutor,行为稍有不同
● 如果总线程数达到 maximumPoolSize
○ 这时不会立刻抛 RejectedExecutionException 异常
○ 而是再次尝试将任务放入队列,如果还失败,才抛出 RejectedExecutionException 异常

源码 tomcat-7.0.42

public void execute(Runnable command, long timeout, TimeUnit unit) {
    submittedCount.incrementAndGet();
    try {
        super.execute(command);
    } catch (RejectedExecutionException rx) {
        if (super.getQueue() instanceof TaskQueue) {
            final TaskQueue queue = (TaskQueue)super.getQueue();
            try {
                if (!queue.force(command, timeout, unit)) {
                    submittedCount.decrementAndGet();
                    throw new RejectedExecutionException("Queue capacity is full.");
                }
            } catch (InterruptedException x) {
                submittedCount.decrementAndGet();
                Thread.interrupted();
                throw new RejectedExecutionException(x);
            }
        } else {
            submittedCount.decrementAndGet();
            throw rx;
        }
    }
}

TaskQueue.java


public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
    if ( parent.isShutdown() ) 
        throw new RejectedExecutionException(
        "Executor not running, can't force a command into the queue"
    );
    return super.offer(o,timeout,unit); //forces the item onto the queue, to be used if the task 
    is rejected
}
Connector 配置

在这里插入图片描述

Executor 线程配置

在这里插入图片描述

在这里插入图片描述

11、Fork/Join线程池

在这里插入图片描述

使用

提交给 Fork/Join 线程池的任务需要继承 RecursiveTask(有返回值)或 RecursiveAction(没有返回值),例如下面定义了一个对 1~n 之间的整数求和的任务

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

public class testThread28 {
    public static void main(String[] args) {
        //然后提交给 ForkJoinPool 来执行
        ForkJoinPool pool = new ForkJoinPool(4);
        System.out.println(pool.invoke(new AddTask1(5)));
    }
}



@Slf4j(topic = "c.AddTask")
class AddTask1 extends RecursiveTask<Integer> {
    int n;

    public AddTask1(int n) {
        this.n = n;
    }

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

    @Override
    protected Integer compute() {
        // 如果 n 已经为 1,可以求得结果了
        if (n == 1) {
            log.debug("join() {}", n);
            return n;
        }

        // 将任务进行拆分(fork)
        AddTask1 t1 = new AddTask1(n - 1);
        t1.fork();
        log.debug("fork() {} + {}", n, t1);

        // 合并(join)结果
        int result = n + t1.join();
        log.debug("join() {} + {} = {}", n, t1, result);
        return result;
    }
}


用图来表示

在这里插入图片描述

细节改进
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

public class testThread28 {
    public static void main(String[] args) {
        //然后提交给 ForkJoinPool 来执行
        ForkJoinPool pool = new ForkJoinPool(4);
        System.out.println(pool.invoke(new AddTask3(1, 10)));


        /**
         * 
         * 15:05:24 [ForkJoinPool-1-worker-3] c.AddTask - fork() {6,8} + {9,10} = ?
         * 15:05:24 [ForkJoinPool-1-worker-2] c.AddTask - fork() {1,3} + {4,5} = ?
         * 15:05:24 [ForkJoinPool-1-worker-1] c.AddTask - fork() {1,5} + {6,10} = ?
         * 15:05:24 [ForkJoinPool-1-worker-0] c.AddTask - fork() {1,2} + {3,3} = ?
         * 15:05:24 [ForkJoinPool-1-worker-1] c.AddTask - join() 4 + 5 = 9
         * 15:05:24 [ForkJoinPool-1-worker-3] c.AddTask - fork() {6,7} + {8,8} = ?
         * 15:05:24 [ForkJoinPool-1-worker-0] c.AddTask - join() 1 + 2 = 3
         * 15:05:24 [ForkJoinPool-1-worker-3] c.AddTask - join() 6 + 7 = 13
         * 15:05:24 [ForkJoinPool-1-worker-1] c.AddTask - join() 3
         * 15:05:24 [ForkJoinPool-1-worker-3] c.AddTask - join() 8
         * 15:05:24 [ForkJoinPool-1-worker-0] c.AddTask - join() {1,2} + {3,3} = 6
         * 15:05:24 [ForkJoinPool-1-worker-3] c.AddTask - join() {6,7} + {8,8} = 21
         * 15:05:24 [ForkJoinPool-1-worker-0] c.AddTask - join() 9 + 10 = 19
         * 15:05:24 [ForkJoinPool-1-worker-2] c.AddTask - join() {1,3} + {4,5} = 15
         * 15:05:24 [ForkJoinPool-1-worker-3] c.AddTask - join() {6,8} + {9,10} = 40
         * 15:05:24 [ForkJoinPool-1-worker-1] c.AddTask - join() {1,5} + {6,10} = 55
         * 55
         */
    }
}



@Slf4j(topic = "c.AddTask")
class AddTask3 extends RecursiveTask<Integer> {

    int begin;
    int end;

    public AddTask3(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }

    @Override
    public String toString() {
        return "{" + begin + "," + end + '}';
    }

    @Override
    protected Integer compute() {
        // 5, 5
        if (begin == end) {
            log.debug("join() {}", begin);
            return begin;
        }
        // 4, 5
        if (end - begin == 1) {
            log.debug("join() {} + {} = {}", begin, end, end + begin);
            return end + begin;
        }

        // 1 5
        int mid = (end + begin) / 2; // 3
        AddTask3 t1 = new AddTask3(begin, mid); // 1,3
        t1.fork();
        AddTask3 t2 = new AddTask3(mid + 1, end); // 4,5
        t2.fork();
        log.debug("fork() {} + {} = ?", t1, t2);
        int result = t1.join() + t2.join();
        log.debug("join() {} + {} = {}", t1, t2, result);
        return result;
    }
}

用图来表示

在这里插入图片描述

异步模式:工作线程🔞

1、定义

在这里插入图片描述

2、饥饿现象:因线程数量的不足

固定大小的线程池和单线程的线程池都会有饥饿线程,带缓冲的线程池不会有饥饿现象

在这里插入图片描述

线程饥饿代码示例


import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j(topic = "c.TestStarvation")
public class TestStarvation {

    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 executorService = Executors.newFixedThreadPool(2);

        executorService.execute(() -> {
            log.debug("处理点餐...");
            Future<String> f = executorService.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        executorService.execute(() -> {
            log.debug("处理点餐...");
            Future<String> f = executorService.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}

解决方法1:添加线程

本来两个线程变成3个线程
解决方法可以增加线程池的大小,不过不是根本解决方案,还是前面提到的,不同的任务类型,采用不同的线程 
池。

ExecutorService executorService = Executors.newFixedThreadPool(3);

解决方法2:不同的任务类型,采用不同的线程池

代码示例

import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j(topic = "c.TestStarvation")
public class TestStarvation {

    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(() -> {
            log.debug("处理点餐...");
            Future<String> f = cookPool.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        waiterPool.execute(() -> {
            log.debug("处理点餐...");
            Future<String> f = cookPool.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}

3、创建多少线程合适

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值