【记录】并发编程 - 学习日志(十)

8.2 ThreadPoolExecutor

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

8.2.1 线程池状态

【草稿纸】- 不严谨

回忆自定义线程池

线程集合(消费者)、任务队列、生产者

消费者从任务队列中获取 task 并执行

生产者向任务队列中添加 task

RUNNING:正常

SHUTDOWN:禁止生产者向任务队列中添加 task

STOP:禁止消费者执行 task

TIDYING:无 task 需被消费者执行

TERMINATED:进程结束

通过比较线程池状态的高 3 位得出:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED

高 1 位为符号位:0 - 整数,1 - 负数

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

// 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;
}

8.2.2 构造方法 

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

1. corePoolSize:要保留在池中的线程数,即使它们处于空闲状态,除非设置了{@code allowCoreThreadTimeOut}

2. maximumPoolSize:池中允许的最大线程数

3. keepAliveTime:当线程数大于核心时,这是多余空闲线程在终止前等待新任务的最长时间。

4. unit:{@code keepAliveTime}参数的时间单位 

5. workQueue:在执行任务之前用于保存任务的队列。此队列将只包含由{@code execute}方法提交的{@codeRunnable}任务。 

6. threadFactory:执行器创建新线程时要使用的工厂

7. handler:当由于达到线程边界和队列容量而阻止执行时要使用的处理程序

【草稿纸】- 不严谨 

线程集合中有核心线程、救急线程

当 corePoolSize = 2; maximumPoolSize = 3; workQueue.size() = 0; 时

线程的创建方式为懒惰初始化,故而线程池初期时是没有线程的

生产者向线程池提交 task 后,[核心]线程才被创建出来用于执行这个 task

当[核心]线程数等于 corePoolSize 时,task 会被添加到 workQueue 中

若 workQueue 为有界队列,当 workQueue 中的 task 数等于 workQueue.size() 时,仍有 task 被提交,则创建[救急]线程用于执行这个 task

若创建 maximumPoolSize - corePoolSize 个救急线程后,仍有 task 被提交,则启用拒绝策略(handler)

核心线程不再执行 task 后,不会释放

救急线程不再执行 task keepAliveTime unit 后,被释放


corePoolSize:核心线程数

maximumPoolSize:允许的最大线程数

keepAliveTime:救急线程不再执行 task 后存活的时间

unit:keepAliveTime 的单位

workQueue:任务队列

threadFactory:给线程起名?存疑

handler:拒绝策略

8.2.3 newFixedThreadPool

newFixedThreadPool(int nThreads)

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

1. 因为核心线程数与最大线程数均为 nThreads, 所以救急线程数为 0

2. 因为救急线程数为 0,所以救急线程存活时间为 0

3. 因为 new LinkedBlockingQueue<Runnable>(),所以任务队列的容量为 Integer.MAX_VALUE

    /**
     * Creates a {@code LinkedBlockingQueue} with a capacity of
     * {@link Integer#MAX_VALUE}.
     */
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

 【举个栗子】

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j(topic = "c.Test1")
public class Test1 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(2);

        service.execute(() -> {
            log.debug("1");
        });

        service.execute(() -> {
            log.debug("2");
        });

        service.execute(() -> {
            log.debug("3");
        });
    }
}
// 某次运行结果

09:54:40 [pool-1-thread-2] c.Test1 - 2
09:54:40 [pool-1-thread-1] c.Test1 - 1
09:54:40 [pool-1-thread-2] c.Test1 - 3

 newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}
// class ThreadPoolExecutor    
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}
// class Executors

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

8.2.4 newCachedThreadPool

// class Executors

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

1. 因为核心线程数为 0 且最大线程数为 Integer.MAX_VALUE,所以救急线程数为 Integer.MAX_VALUE

2. 救急线程执行完 task 后的存活时间为 60s

3. 【个人理解】SynchronousQueue

        任务队列的容量为 0

        生产者向线程池中提交任务选择无限制等待方式

         唯有线程池中有空闲线程时,生产者向线程池中提交任务才不会被阻塞

【来吧,模拟展示】 SynchronousQueue

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.SynchronousQueue;

@Slf4j(topic = "c.Test2")
public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        SynchronousQueue<Integer> queue = new SynchronousQueue<>();

        new Thread(() ->{
            try {
                log.debug("t1-1. putting...");
                queue.put(1);
                log.debug("t1-1. put...");

                log.debug("t1-2. putting...");
                queue.put(2);
                log.debug("t1-2. put...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        Thread.sleep(1000);

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

        Thread.sleep(1000);

        new Thread(() ->{
            try {
                log.debug("t3. taking...");
                queue.take();
                log.debug("t3. toke...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t3").start();
    }
}
// 某次运行结果

11:11:42 [t1] c.Test2 - t1-1. putting...
11:11:43 [t2] c.Test2 - t2. taking...
11:11:43 [t2] c.Test2 - t2. toke...
11:11:43 [t1] c.Test2 - t1-1. put...
11:11:43 [t1] c.Test2 - t1-2. putting...
11:11:44 [t3] c.Test2 - t3. taking...
11:11:44 [t3] c.Test2 - t3. toke...
11:11:44 [t1] c.Test2 - t1-2. put...

进程已结束,退出代码 0

8.2.5 newSingleThreadExecutor 

// class Executors

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

1. 因为核心线程数为 1 且最大线程数为 1,所以救急线程数为 0

2. 因为救急线程数为 0,所以救急线程存活时间为 0

3. 因为 new LinkedBlockingQueue<Runnable>(),所以任务队列的容量为 Integer.MAX_VALUE

【抛出问题】

当 newFixedThreadPool(int nThreads) 中 nThreads 为 1 时,newFixedThreadPool 与 newSingleThreadExecutor 有什么区别?

区别一:newFixedThreadPool 中核心线程数可被修改;newSingleThreadExecutor 中核心线程数固定为 1,不允许被修改

区别二:newFixedThreadPool 中直接返回 new ThreadPoolExecutor;newSingleThreadExecutor 中返回由 new FinalizableDelegatedExecutorService 包装后的 new ThreadPoolExecutor 

8.2.6 提交任务 

1. execute 和 submit

// 无返回值
void execute(Runnable command)

// 有返回值
<T> Future<T> submit(Callable<T> task)

【举个栗子】

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

@Slf4j(topic = "c.Test3")
public class Test3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        Future<String> result = pool.submit(() -> {
            log.debug("task begin...");
            Thread.sleep(1000);
            log.debug("task end...");
            return "task result...";
        });

        log.debug("result:{}", result.get());
    }
}
// 某次运行结果

16:50:37 [pool-1-thread-1] c.Test3 - task begin...
16:50:38 [pool-1-thread-1] c.Test3 - task end...
16:50:38 [main] c.Test3 - result:task result...

2. invokeAll

执行并返回所有被提交给线程池的 task

// 无限时
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)

// 超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)

【举个栗子 - invokeAll[无限时]】

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j(topic = "c.Test4")
public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        List<Future<String>> results = pool.invokeAll(Arrays.asList(
                () -> {
                    log.debug("task1 begin...");
                    Thread.sleep(1000);
                    log.debug("task1 end...");
                    return "task1 result...";
                },
                () -> {
                    log.debug("task2 begin...");
                    Thread.sleep(500);
                    log.debug("task2 end...");
                    return "task2 result...";
                },
                () -> {
                    log.debug("task3 begin...");
                    Thread.sleep(2000);
                    log.debug("task3 end...");
                    return "task3 result...";
                }
                )
        );

        results.forEach(f -> {
            try {
                log.debug("result:{}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}
// 某次运行结果

17:10:04 [pool-1-thread-1] c.Test4 - task1 begin...
17:10:04 [pool-1-thread-2] c.Test4 - task2 begin...
17:10:04 [pool-1-thread-2] c.Test4 - task2 end...
17:10:04 [pool-1-thread-2] c.Test4 - task3 begin...
17:10:05 [pool-1-thread-1] c.Test4 - task1 end...
17:10:06 [pool-1-thread-2] c.Test4 - task3 end...
17:10:06 [main] c.Test4 - result:task1 result...
17:10:06 [main] c.Test4 - result:task2 result...
17:10:06 [main] c.Test4 - result:task3 result...

3. invokeAny

返回第一个被执行完的 task 的执行结果,并打断线程池中正在执行 task 的线程,且不再获取并执行任务队列中的 task

// 无限时
<T> T invokeAny(Collection<? extends Callable<T>> tasks)

// 超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)

【举个栗子 - invokeAny[无限时]】 

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j(topic = "c.Test5")
public class Test5 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        String result = pool.invokeAny(Arrays.asList(
                () -> {
                    log.debug("task1 begin...");
                    Thread.sleep(1000);
                    log.debug("task1 end...");
                    return "task1 result...";
                },
                () -> {
                    log.debug("task2 begin...");
                    Thread.sleep(500);
                    log.debug("task2 end...");
                    return "task2 result...";
                },
                () -> {
                    log.debug("task3 begin...");
                    Thread.sleep(2000);
                    log.debug("task3 end...");
                    return "task3 result...";
                },
                () -> {
                    log.debug("task4 begin...");
                    Thread.sleep(1500);
                    log.debug("task4 end...");
                    return "task4 result...";
                }
        ));

        log.debug("result:{}", result);
    }
}
// 某次运行结果

17:19:25 [pool-1-thread-1] c.Test5 - task1 begin...
17:19:25 [pool-1-thread-2] c.Test5 - task2 begin...
17:19:26 [pool-1-thread-2] c.Test5 - task2 end...
17:19:26 [pool-1-thread-2] c.Test5 - task3 begin...
17:19:26 [main] c.Test5 - result:task2 result...

 8.2.7 线程池状态

1. void shutdown() 

修改线程池状态为 SHUTDOWN

该状态下,

        执行已提交线程池的任务,线程池不再接受新的任务

        生产者可继续运行,无需等待线程池执行完已提交的任务

【举个栗子】 

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j(topic = "c.Test6")
public class Test6 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        Future<String> result1 = pool.submit(() -> {
            log.debug("task1 begin...");
            Thread.sleep(1000);
            log.debug("task1 end...");
            return "task1 result...";
        });

        Future<String> result2 = pool.submit(() -> {
            log.debug("task2 begin...");
            Thread.sleep(1000);
            log.debug("task2 end...");
            return "task2 result...";
        });

        Future<String> result3 = pool.submit(() -> {
            log.debug("task3 begin...");
            Thread.sleep(1000);
            log.debug("task3 end...");
            return "task3 result...";
        });

        log.debug("shutdown...");
        pool.shutdown();
        log.debug("other...");
        Future<String> result4 = pool.submit(() -> {
            log.debug("task4 begin...");
            Thread.sleep(1000);
            log.debug("task4 end...");
            return "task4 result...";
        });
    }
}
// 某次运行结果

18:00:06 [main] c.Test6 - shutdown...
18:00:06 [pool-1-thread-2] c.Test6 - task2 begin...
18:00:06 [pool-1-thread-1] c.Test6 - task1 begin...
18:00:06 [main] c.Test6 - other...
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@256216b3 rejected from java.util.concurrent.ThreadPoolExecutor@2a18f23c[Shutting down, pool size = 2, active threads = 2, queued tasks = 1, completed tasks = 0]
	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:134)
	at com.rui.eight.Test6.main(Test6.java:39)
18:00:07 [pool-1-thread-1] c.Test6 - task1 end...
18:00:07 [pool-1-thread-2] c.Test6 - task2 end...
18:00:07 [pool-1-thread-1] c.Test6 - task3 begin...
18:00:08 [pool-1-thread-1] c.Test6 - task3 end...

进程已结束,退出代码 1

若生产者希望等待,可通过 boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException 

【举个栗子】

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test6")
public class Test6 {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        Future<String> result1 = pool.submit(() -> {
            log.debug("task1 begin...");
            Thread.sleep(1000);
            log.debug("task1 end...");
            return "task1 result...";
        });

        Future<String> result2 = pool.submit(() -> {
            log.debug("task2 begin...");
            Thread.sleep(1000);
            log.debug("task2 end...");
            return "task2 result...";
        });

        Future<String> result3 = pool.submit(() -> {
            log.debug("task3 begin...");
            Thread.sleep(1000);
            log.debug("task3 end...");
            return "task3 result...";
        });

        log.debug("shutdown...");
        pool.shutdown();
        pool.awaitTermination(3, TimeUnit.SECONDS);
        log.debug("other...");
    }
}
// 某次运行结果

18:08:48 [main] c.Test6 - shutdown...
18:08:48 [pool-1-thread-2] c.Test6 - task2 begin...
18:08:48 [pool-1-thread-1] c.Test6 - task1 begin...
18:08:49 [pool-1-thread-2] c.Test6 - task2 end...
18:08:49 [pool-1-thread-1] c.Test6 - task1 end...
18:08:49 [pool-1-thread-1] c.Test6 - task3 begin...
18:08:50 [pool-1-thread-1] c.Test6 - task3 end...
18:08:50 [main] c.Test6 - other...

进程已结束,退出代码 0

 2. List<Runnable> shutdownNow()

修改线程池状态为 STOP

该状态下,线程池中不再执行 task

【举个栗子】

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j(topic = "c.Test6")
public class Test6 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        Future<String> result1 = pool.submit(() -> {
            log.debug("task1 begin...");
            Thread.sleep(1000);
            log.debug("task1 end...");
            return "task1 result...";
        });

        Future<String> result2 = pool.submit(() -> {
            log.debug("task2 begin...");
            Thread.sleep(1000);
            log.debug("task2 end...");
            return "task2 result...";
        });

        Future<String> result3 = pool.submit(() -> {
            log.debug("task3 begin...");
            Thread.sleep(1000);
            log.debug("task3 end...");
            return "task3 result...";
        });

        log.debug("shutdownNow...");
        pool.shutdownNow();
        log.debug("other...");
    }
}
// 某次运行结果

18:02:37 [main] c.Test6 - shutdownNow...
18:02:37 [pool-1-thread-2] c.Test6 - task2 begin...
18:02:37 [pool-1-thread-1] c.Test6 - task1 begin...
18:02:37 [main] c.Test6 - other...

进程已结束,退出代码 0

 3. boolean isShutdown()

若线程池状态为 RUNNABLE,返回 true;反之,返回 false

4. boolean isTerminated()

若线程池状态为 TERMINATED,返回 true;反之,返回 false

8.2.8 Worker Thread 模式

假设,有一家餐馆,将线程看做是员工,将任务看做是顾客

餐馆刚刚开业,招聘了 2 名员工,但并未对他们安排特定的分工(啥都干)

package com.rui.eight;

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.Test7")
public class Test7 {
    static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");

    static String cooking() {
        return MENU.get(new Random().nextInt(MENU.size()));
    }

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

        pool.execute(() -> {
            log.debug("接单...");
            Future<String> result = pool.submit(() -> {
                log.debug("烹饪...");
                return cooking();
            });
            try {
                log.debug("出餐:{}", result.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}
// 某次运行结果

18:44:21 [pool-1-thread-1] c.Test7 - 接单...
18:44:21 [pool-1-thread-2] c.Test7 - 烹饪...
18:44:21 [pool-1-thread-1] c.Test7 - 出餐:地三鲜

 顾客增多...

package com.rui.eight;

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.Test7")
public class Test7 {
    static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");

    static String cooking() {
        return MENU.get(new Random().nextInt(MENU.size()));
    }

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

        pool.execute(() -> {
            log.debug("接单...");
            Future<String> result = pool.submit(() -> {
                log.debug("烹饪...");
                return cooking();
            });
            try {
                log.debug("出餐:{}", result.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });

        pool.execute(() -> {
            log.debug("接单...");
            Future<String> result = pool.submit(() -> {
                log.debug("烹饪...");
                return cooking();
            });
            try {
                log.debug("出餐:{}", result.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}
// 某次运行结果

18:46:38 [pool-1-thread-2] c.Test7 - 接单...
18:46:38 [pool-1-thread-1] c.Test7 - 接单...

问题暴露 - 饥饿

员工 A 提议:招工

治标不治本

员工 B 提议:分工

package com.rui.eight;

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.Test7")
public class Test7 {
    static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");

    static String cooking() {
        return MENU.get(new Random().nextInt(MENU.size()));
    }

    public static void main(String[] args) {
        ExecutorService waiterPool = Executors.newFixedThreadPool(1);
        ExecutorService chefPool = Executors.newFixedThreadPool(1);

        waiterPool.execute(() -> {
            log.debug("接单...");
            Future<String> result = chefPool.submit(() -> {
                log.debug("烹饪...");
                return cooking();
            });
            try {
                log.debug("出餐:{}", result.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });

        waiterPool.execute(() -> {
            log.debug("接单...");
            Future<String> result = chefPool.submit(() -> {
                log.debug("烹饪...");
                return cooking();
            });
            try {
                log.debug("出餐:{}", result.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}
// 某次运行结果

18:51:13 [pool-1-thread-1] c.Test7 - 接单...
18:51:13 [pool-2-thread-1] c.Test7 - 烹饪...
18:51:13 [pool-1-thread-1] c.Test7 - 出餐:辣子鸡丁
18:51:13 [pool-1-thread-1] c.Test7 - 接单...
18:51:13 [pool-2-thread-1] c.Test7 - 烹饪...
18:51:13 [pool-1-thread-1] c.Test7 - 出餐:宫保鸡丁

创建多少线程池合适

1. CPU 密集型运算

CPU core 数 + 1

2. I/O 密集型运算

CPU core 数 * 期望 CPU 利用率 * 总时间(CPU 计算时间 + 等待时间)/ CPU 计算时间

【举个栗子】

4 核 CPU 计算时间为 10%,等待时间为 90%,期望 CPU 利用率为 100%

4 * 100% * (10% + 90%)/ 10%

8.2.9 Timer

所有任务皆由同一个线程调度

因此,任务的执行为串行执行,若执行任务时出现延迟或异常,会影响之后任务的执行。

【示例】

『任务1』延时

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.Timer;
import java.util.TimerTask;

@Slf4j(topic = "c.Test8")
public class Test8 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                try {
                    log.debug("task1 begin...");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                log.debug("task2 begin...");
            }
        };
        // 使用 timer 添加两个任务,希望它们都在 1s 后执行
        // 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的延时,影响了『任务2』的执行
        timer.schedule(task1, 1000);
        timer.schedule(task2, 1000);
    }
}
// 某次运行结果

08:40:14 [Timer-0] c.Test8 - task1 begin...
08:40:16 [Timer-0] c.Test8 - task2 begin...

 『任务1』异常

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.Timer;
import java.util.TimerTask;

@Slf4j(topic = "c.Test8")
public class Test8 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                log.debug("task1 begin...");
                int i = 1 / 0;
            }
        };
        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                log.debug("task2 begin...");
            }
        };
        // 使用 timer 添加两个任务,希望它们都在 1s 后执行
        // 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的异常,影响了『任务2』的执行
        timer.schedule(task1, 1000);
        timer.schedule(task2, 1000);
    }
}
// 某次运行结果

08:42:35 [Timer-0] c.Test8 - task1 begin...
Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero
	at com.rui.eight.Test8$1.run(Test8.java:16)
	at java.util.TimerThread.mainLoop(Timer.java:555)
	at java.util.TimerThread.run(Timer.java:505)

进程已结束,退出代码 0

8.2.10 newScheduledThreadPool 

所有任务可由不同线程调度

执行任务时出现异常,不会影响之后任务的执行。

【示例】

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test9")
public class Test9 {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);

        pool.schedule(() -> {
            log.debug("task1 begin...");
            int i = 1 / 0;
        }, 1, TimeUnit.SECONDS);
        pool.schedule(() -> {
            log.debug("task2 begin...");
        }, 1, TimeUnit.SECONDS);
    }
}
// 某次运行结果

09:04:27 [pool-1-thread-1] c.Test9 - task1 begin...
09:04:27 [pool-1-thread-1] c.Test9 - task2 begin...

scheduleAtFixedRate

周期性

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

Runnable command:要执行的任务

long initialDelay:延迟第一次执行的时间 

long period:连续执行之间的时间 

TimeUnit unit:long period 的单位

【示例】

如果此任务的任何执行时间都超过其周期,则后续执行可能会延迟开始,但不会同时执行。

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test9")
public class Test9 {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);

        log.debug("start...");
        pool.scheduleAtFixedRate(() -> {
            try {
                log.debug("running...");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 1, 1, TimeUnit.SECONDS);
    }
}
// 某次运行结果

09:15:23 [main] c.Test9 - start...
09:15:24 [pool-1-thread-1] c.Test9 - running...
09:15:26 [pool-1-thread-1] c.Test9 - running...
09:15:28 [pool-1-thread-1] c.Test9 - running...
09:15:30 [pool-1-thread-1] c.Test9 - running...
// ...

【应用】 

需求:从下周五 9:59:30 开始每隔 1 秒执行一次任务

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j(topic = "c.Test11")
public class Test11 {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);

        LocalDateTime now = LocalDateTime.now();
        log.debug("now:{}", now);

        LocalDateTime want = now.withHour(9).withMinute(59).withSecond(30).withNano(0).with(DayOfWeek.FRIDAY);

        if (now.compareTo(want) > 0) {
            want = want.plusWeeks(1);
        }

        long initialDelay = Duration.between(now, want).toMillis();
        long period = 1000;
        pool.scheduleAtFixedRate(() -> {
            log.debug("running...");
        }, initialDelay, period, TimeUnit.MILLISECONDS);
    }
}
// 某次运行结果

09:59:14 [main] c.Test11 - now:2023-09-22T09:59:14.806
09:59:30 [pool-1-thread-1] c.Test11 - running...
09:59:31 [pool-1-thread-1] c.Test11 - running...
09:59:32 [pool-1-thread-1] c.Test11 - running...
09:59:33 [pool-1-thread-1] c.Test11 - running...
09:59:34 [pool-1-thread-1] c.Test11 - running...
09:59:35 [pool-1-thread-1] c.Test11 - running...
09:59:36 [pool-1-thread-1] c.Test11 - running...
09:59:37 [pool-1-thread-1] c.Test11 - running...

scheduleWithFixedDelay

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                 long initialDelay,
                                                 long delay,
                                                 TimeUnit unit);

Runnable command:要执行的任务

long initialDelay:延迟第一次执行的时间 

long delay:一个执行终止到下一个执行开始之间的延迟

TimeUnit unit:long period 的单位

【示例】

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test9")
public class Test9 {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);

        log.debug("start...");
        pool.scheduleWithFixedDelay(()->{
            try {
                log.debug("running...");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },1,1,TimeUnit.SECONDS);
    }
}
// 某次运行结果

09:17:48 [main] c.Test9 - start...
09:17:49 [pool-1-thread-1] c.Test9 - running...
09:17:52 [pool-1-thread-1] c.Test9 - running...
09:17:55 [pool-1-thread-1] c.Test9 - running...
09:17:58 [pool-1-thread-1] c.Test9 - running...
09:18:01 [pool-1-thread-1] c.Test9 - running...

8.2.11 正确处理执行任务异常

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j(topic = "c.Test10")
public class Test10 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(1);

        pool.submit(() -> {
            log.debug("task begin...");
            int i = 1 / 0;
        });
    }
}
// 某次运行结果

09:29:54 [pool-1-thread-1] c.Test10 - task begin...

【抛出疑问】控制台未处理异常,如何正确处理执行任务异常?

方法一:try / catch

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j(topic = "c.Test10")
public class Test10 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(1);

        pool.submit(() -> {
            try {
                log.debug("task begin...");
                int i = 1 / 0;
            } catch (Exception e) {
                log.error("", e);
            }
        });
    }
}
// 某次运行结果

09:33:38 [pool-1-thread-1] c.Test10 - task begin...
09:33:38 [pool-1-thread-1] c.Test10 - 
java.lang.ArithmeticException: / by zero
	at com.rui.eight.Test10.lambda$main$0(Test10.java:16)
	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:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

方法二: Future

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j(topic = "c.Test10")
public class Test10 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(1);

        Future<Boolean> result = pool.submit(() -> {
            log.debug("task begin...");
            int i = 1 / 0;
            return true;
        });

        log.debug("{}", result.get());
    }
}
// 某次运行结果

09:36:28 [pool-1-thread-1] c.Test10 - task begin...
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 com.rui.eight.Test10.main(Test10.java:21)
Caused by: java.lang.ArithmeticException: / by zero
	at com.rui.eight.Test10.lambda$main$0(Test10.java:17)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

8.3 fork / join 

需求:计算 1 ~ n 的和

思路:递归、拆分

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

@Slf4j(topic = "c.Test12")
public class Test12 {
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool(4);
        System.out.println(pool.invoke(new MyTask(5)));
    }
}

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

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

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

    @Override
    protected Integer compute() {
        if (n == 1) {
            log.debug("fork({})/join({}) = {}", n, n, n);
            return n;
        }

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

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

10:52:57 [ForkJoinPool-1-worker-1] c.MyTask - 5 + fork(4)
10:52:57 [ForkJoinPool-1-worker-0] c.MyTask - 2 + fork(1)
10:52:57 [ForkJoinPool-1-worker-2] c.MyTask - 4 + fork(3)
10:52:57 [ForkJoinPool-1-worker-3] c.MyTask - 3 + fork(2)
10:52:57 [ForkJoinPool-1-worker-0] c.MyTask - fork(1)/join(1) = 1
10:52:57 [ForkJoinPool-1-worker-0] c.MyTask - 2 + join(1) = 3
10:52:57 [ForkJoinPool-1-worker-3] c.MyTask - 3 + join(2) = 6
10:52:57 [ForkJoinPool-1-worker-2] c.MyTask - 4 + join(3) = 10
10:52:57 [ForkJoinPool-1-worker-1] c.MyTask - 5 + join(4) = 15
15

进程已结束,退出代码 0

 优化思路:二分法、拆分

package com.rui.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

@Slf4j(topic = "c.Test12")
public class Test12 {
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool(4);
        System.out.println(pool.invoke(new MyTask(1, 5)));
    }
}

@Slf4j(topic = "c.MyTask")
class MyTask extends RecursiveTask<Integer> {
    private int begin;
    private int end;

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

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

    @Override
    protected Integer compute() {
        if (begin == end) {
            log.debug("fork({},{})/join{{},{}} = {}", begin, end, begin, end, begin);
            return begin;
        }
        if (begin + 1 == end) {
            log.debug("fork({},{})/join{{},{}} = {}", begin, end, begin, end, begin + end);
            return begin + end;
        }
        int mid = (begin + end) / 2;
        MyTask t1 = new MyTask(begin, mid);
        t1.fork();
        MyTask t2 = new MyTask(mid + 1, end);
        t2.fork();
        log.debug("fork{} + fork{}", t1, t2);

        int result = t1.join() + t2.join();
        log.debug("join{} + join{} = {}", t1, t2, result);
        return result;
    }
}
// 某次运行结果

11:07:54 [ForkJoinPool-1-worker-3] c.MyTask - fork(4,5)/join{4,5} = 9
11:07:54 [ForkJoinPool-1-worker-0] c.MyTask - fork(1,2)/join{1,2} = 3
11:07:54 [ForkJoinPool-1-worker-1] c.MyTask - fork(1,3) + fork(4,5)
11:07:54 [ForkJoinPool-1-worker-2] c.MyTask - fork(1,2) + fork(3,3)
11:07:54 [ForkJoinPool-1-worker-3] c.MyTask - fork(3,3)/join{3,3} = 3
11:07:54 [ForkJoinPool-1-worker-2] c.MyTask - join(1,2) + join(3,3) = 6
11:07:54 [ForkJoinPool-1-worker-1] c.MyTask - join(1,3) + join(4,5) = 15
15

进程已结束,退出代码 0

说些废话

本篇文章为博主日常学习记录,故而会概率性地存在各种错误,若您在浏览过程中发现一些,请在评论区指正,望我们共同进步,谢谢!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值